From 232176210981a8e21793f6096dd651eba29caf8e Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 26 Dec 2018 12:40:58 -0800 Subject: Populate MsiAssemblyName table Fixes wixtoolset/issues#5865 --- .../Bind/AssemblyName.cs | 60 +++++ .../Bind/AssemblyNameReader.cs | 192 ++++++++++++++++ .../Bind/UpdateFileFacadesCommand.cs | 256 ++++----------------- .../CLR/Interop/CLRInterop.cs | 147 ------------ .../WixToolset.Core.WindowsInstaller.csproj | 4 + .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 24 +- .../WixToolsetTest.CoreIntegration.csproj | 2 +- 7 files changed, 325 insertions(+), 360 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs new file mode 100644 index 00000000..0df1a7e9 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.Text; + + internal class AssemblyName + { + public AssemblyName(string name, string culture, string version, string fileVersion, string architecture, string publicKeyToken, string type) + { + this.Name = name; + this.Culture = culture ?? "neutral"; + this.Version = version; + this.FileVersion = fileVersion; + this.Architecture = architecture; + + this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken); + this.PublicKeyToken = publicKeyToken ?? "null"; + this.Type = type; + } + + public string Name { get; } + + public string Culture { get; } + + public string Version { get; } + + public string FileVersion { get; } + + public string Architecture { get; } + + public string PublicKeyToken { get; } + + public bool StrongNamedSigned { get; } + + public string Type { get; } + + public string GetFullName() + { + var assemblyName = new StringBuilder(); + + assemblyName.Append(this.Name); + assemblyName.Append(", Version="); + assemblyName.Append(this.Version); + assemblyName.Append(", Culture="); + assemblyName.Append(this.Culture); + assemblyName.Append(", PublicKeyToken="); + assemblyName.Append(this.PublicKeyToken); + + if (!String.IsNullOrEmpty(this.Architecture)) + { + assemblyName.Append(", ProcessorArchitecture="); + assemblyName.Append(this.Architecture); + } + + return assemblyName.ToString(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs new file mode 100644 index 00000000..4815cb35 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs @@ -0,0 +1,192 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.IO; + using System.Reflection.Metadata; + using System.Reflection.PortableExecutable; + using System.Security.Cryptography; + using System.Text; + using System.Xml; + using System.Xml.XPath; + using WixToolset.Data; + + internal static class AssemblyNameReader + { + public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion) + { + try + { + using (var stream = File.OpenRead(assemblyPath)) + using (var peReader = new PEReader(stream)) + { + var reader = peReader.GetMetadataReader(); + var headers = peReader.PEHeaders; + + var assembly = reader.GetAssemblyDefinition(); + var attributes = assembly.GetCustomAttributes(); + + var name = ReadString(reader, assembly.Name); + var culture = ReadString(reader, assembly.Culture); + var architecture = headers.PEHeader.Magic == PEMagic.PE32Plus ? "x64" : (headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit ? "x86" : null; + var version = assembly.Version.ToString(); + var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey); + + // There is a bug in v1 fusion that requires the assembly's "version" attribute + // to be equal to or longer than the "fileVersion" in length when its present; + // the workaround is to prepend zeroes to the last version number in the assembly + // version. + var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0); + if (targetNetfx1 && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length) + { + var versionParts = version.Split('.'); + + if (versionParts.Length > 0) + { + var padding = new string('0', fileVersion.Length - version.Length); + + versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]); + version = String.Join(".", versionParts); + } + } + + return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null); + } + } + catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException) + { + throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}")); + } + } + + public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath) + { + string win32Type = null; + string win32Name = null; + string win32Version = null; + string win32ProcessorArchitecture = null; + string win32PublicKeyToken = null; + + // Loading the dom is expensive we want more performant APIs than the DOM + // Navigator is cheaper than dom. Perhaps there is a cheaper API still. + try + { + var doc = new XPathDocument(manifestPath); + var nav = doc.CreateNavigator(); + nav.MoveToRoot(); + + // This assumes a particular schema for a win32 manifest and does not + // provide error checking if the file does not conform to schema. + // The fallback case here is that nothing is added to the MsiAssemblyName + // table for an out of tolerance Win32 manifest. Perhaps warnings needed. + if (nav.MoveToFirstChild()) + { + while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") + { + nav.MoveToNext(); + } + + if (nav.MoveToFirstChild()) + { + var hasNextSibling = true; + while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) + { + hasNextSibling = nav.MoveToNext(); + } + + if (!hasNextSibling) + { + throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath)); + } + + if (nav.MoveToAttribute("type", String.Empty)) + { + win32Type = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("name", String.Empty)) + { + win32Name = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("version", String.Empty)) + { + win32Version = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("processorArchitecture", String.Empty)) + { + win32ProcessorArchitecture = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("publicKeyToken", String.Empty)) + { + win32PublicKeyToken = nav.Value; + nav.MoveToParent(); + } + } + } + } + catch (FileNotFoundException fe) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest")); + } + catch (XmlException xe) + { + throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message)); + } + + return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type); + } + + private static string ReadString(MetadataReader reader, StringHandle handle) + { + return handle.IsNil ? null : reader.GetString(handle); + } + + private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle) + { + if (handle.IsNil) + { + return null; + } + + var bytes = reader.GetBlobBytes(handle); + if (bytes.Length == 0) + { + return null; + } + + var result = new StringBuilder(); + + // If we have the full public key, calculate the public key token from the + // last 8 bytes (in reverse order) of the public key's SHA1 hash. + if (bytes.Length > 8) + { + using (var sha1 = SHA1.Create()) + { + var hash = sha1.ComputeHash(bytes); + + for (var i = 1; i <= 8; ++i) + { + result.Append(hash[hash.Length - i].ToString("X2")); + } + } + } + else + { + foreach (var b in bytes) + { + result.Append(b.ToString("X2")); + } + } + + return result.ToString(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 10eae8f8..81300322 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -8,9 +8,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.Globalization; using System.IO; using System.Linq; - using System.Xml; - using System.Xml.XPath; - using WixToolset.Clr.Interop; using WixToolset.Core.Bind; using WixToolset.Data; using WixToolset.Data.Tuples; @@ -45,7 +42,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { - foreach (FileFacade file in this.UpdateFileFacades) + foreach (var file in this.UpdateFileFacades) { this.UpdateFileFacade(file); } @@ -86,7 +83,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (Int32.MaxValue < fileStream.Length) { @@ -224,136 +221,47 @@ namespace WixToolset.Core.WindowsInstaller.Bind // If this is a CLR assembly, load the assembly and get the assembly name information if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) { - bool targetNetfx1 = false; - var assemblyNameValues = new Dictionary(); - - Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; - var result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out var referenceIdentity); - if (0 == result && null != referenceIdentity) + try { - var imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); - if (null != imageRuntimeVersion) - { - targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); - } + var assemblyName = AssemblyNameReader.ReadAssembly(file.File.SourceLineNumbers, fileInfo.FullName, version); - string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral"; - assemblyNameValues.Add("Culture", culture); + this.SetMsiAssemblyName(assemblyNameTuples, file, "name", assemblyName.Name); + this.SetMsiAssemblyName(assemblyNameTuples, file, "culture", assemblyName.Culture); + this.SetMsiAssemblyName(assemblyNameTuples, file, "version", assemblyName.Version); - string name = referenceIdentity.GetAttribute(null, "Name"); - if (null != name) + if (!String.IsNullOrEmpty(assemblyName.Architecture)) { - assemblyNameValues.Add("Name", name); + this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", assemblyName.Architecture); } + // TODO: WiX v3 seemed to do this but not clear it should actually be done. + //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) + //{ + // this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); + //} - string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); - if (null != processorArchitecture) + if (assemblyName.StrongNamedSigned) { - assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); - } - - string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); - if (null != publicKeyToken) - { - bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); - - // Managed code expects "null" instead of "neutral", and - // this won't be installed to the GAC since it's not signed anyway. - assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); - assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); + this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", assemblyName.PublicKeyToken); } else if (file.WixFile.File_AssemblyApplication == null) { throw new WixException(ErrorMessages.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component_)); } - string assemblyVersion = referenceIdentity.GetAttribute(null, "Version"); - if (null != version) + if (!String.IsNullOrEmpty(assemblyName.FileVersion)) { - assemblyNameValues.Add("Version", assemblyVersion); + this.SetMsiAssemblyName(assemblyNameTuples, file, "fileVersion", assemblyName.FileVersion); } - } - else - { - this.Messaging.Write(ErrorMessages.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); - return; - } - - if (assemblyNameValues.TryGetValue("name", out var value)) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "name", value); - } - - if (!String.IsNullOrEmpty(version)) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "fileVersion", version); - } - if (assemblyNameValues.ContainsKey("version")) - { - string assemblyVersion = assemblyNameValues["version"]; - - if (!targetNetfx1) + // add the assembly name to the information cache + if (null != this.VariableCache) { - // There is a bug in v1 fusion that requires the assembly's "version" attribute - // to be equal to or longer than the "fileVersion" in length when its present; - // the workaround is to prepend zeroes to the last version number in the assembly - // version. - if (null != version && version.Length > assemblyVersion.Length) - { - string padding = new string('0', version.Length - assemblyVersion.Length); - string[] assemblyVersionNumbers = assemblyVersion.Split('.'); - - if (assemblyVersionNumbers.Length > 0) - { - assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); - assemblyVersion = String.Join(".", assemblyVersionNumbers); - } - } + this.VariableCache[$"assemblyfullname.{file.File.File}"] = assemblyName.GetFullName(); } - - this.SetMsiAssemblyName(assemblyNameTuples, file, "version", assemblyVersion); - } - - if (assemblyNameValues.ContainsKey("culture")) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "culture", assemblyNameValues["culture"]); - } - - if (assemblyNameValues.ContainsKey("publicKeyToken")) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); - } - - if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); - } - - if (assemblyNameValues.ContainsKey("processorArchitecture")) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); } - - // add the assembly name to the information cache - if (null != this.VariableCache) + catch (WixException e) { - string fileId = file.File.File; - string key = String.Concat("assemblyfullname.", fileId); - string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); - if (assemblyNameValues.ContainsKey("processorArchitecture")) - { - assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); - } - - this.VariableCache[key] = assemblyName; - - // Add entries with the preserved case publicKeyToken - string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); - this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); - - string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); - this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; + this.Messaging.Write(e.Error); } } else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType) @@ -361,114 +269,44 @@ namespace WixToolset.Core.WindowsInstaller.Bind // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through // all files like this. Even though this is a rare case it looks like we might be able to index the // file earlier. - FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.File_AssemblyManifest, StringComparison.Ordinal)); + var fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.File_AssemblyManifest, StringComparison.Ordinal)); if (null == fileManifest) { this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.File_AssemblyManifest)); } - string win32Type = null; - string win32Name = null; - string win32Version = null; - string win32ProcessorArchitecture = null; - string win32PublicKeyToken = null; - - // loading the dom is expensive we want more performant APIs than the DOM - // Navigator is cheaper than dom. Perhaps there is a cheaper API still. try { - XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source.Path); - XPathNavigator nav = doc.CreateNavigator(); - nav.MoveToRoot(); - - // this assumes a particular schema for a win32 manifest and does not - // provide error checking if the file does not conform to schema. - // The fallback case here is that nothing is added to the MsiAssemblyName - // table for an out of tolerance Win32 manifest. Perhaps warnings needed. - if (nav.MoveToFirstChild()) - { - while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") - { - nav.MoveToNext(); - } + var assemblyName = AssemblyNameReader.ReadAssemblyManifest(file.File.SourceLineNumbers, fileManifest.WixFile.Source.Path); - if (nav.MoveToFirstChild()) - { - bool hasNextSibling = true; - while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) - { - hasNextSibling = nav.MoveToNext(); - } - if (!hasNextSibling) - { - this.Messaging.Write(ErrorMessages.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source.Path)); - return; - } - - if (nav.MoveToAttribute("type", String.Empty)) - { - win32Type = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("name", String.Empty)) - { - win32Name = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("version", String.Empty)) - { - win32Version = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("processorArchitecture", String.Empty)) - { - win32ProcessorArchitecture = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("publicKeyToken", String.Empty)) - { - win32PublicKeyToken = nav.Value; - nav.MoveToParent(); - } - } + if (!String.IsNullOrEmpty(assemblyName.Name)) + { + this.SetMsiAssemblyName(assemblyNameTuples, file, "name", assemblyName.Name); } - } - catch (FileNotFoundException fe) - { - this.Messaging.Write(ErrorMessages.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source.Path), fe.FileName, "AssemblyManifest")); - } - catch (XmlException xe) - { - this.Messaging.Write(ErrorMessages.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source.Path), "manifest", xe.Message)); - } - if (!String.IsNullOrEmpty(win32Name)) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "name", win32Name); - } + if (!String.IsNullOrEmpty(assemblyName.Version)) + { + this.SetMsiAssemblyName(assemblyNameTuples, file, "version", assemblyName.Version); + } - if (!String.IsNullOrEmpty(win32Version)) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "version", win32Version); - } + if (!String.IsNullOrEmpty(assemblyName.Type)) + { + this.SetMsiAssemblyName(assemblyNameTuples, file, "type", assemblyName.Type); + } - if (!String.IsNullOrEmpty(win32Type)) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "type", win32Type); - } + if (!String.IsNullOrEmpty(assemblyName.Architecture)) + { + this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", assemblyName.Architecture); + } - if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) - { - this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", win32ProcessorArchitecture); + if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) + { + this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", assemblyName.PublicKeyToken); + } } - - if (!String.IsNullOrEmpty(win32PublicKeyToken)) + catch (WixException e) { - this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", win32PublicKeyToken); + this.Messaging.Write(e.Error); } } } @@ -500,7 +338,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // override directly authored value var lookup = String.Concat(file.File.Component_, "/", name); - if (assemblyNameTuples.TryGetValue(lookup, out var assemblyNameRow)) + if (!assemblyNameTuples.TryGetValue(lookup, out var assemblyNameRow)) { assemblyNameRow = new MsiAssemblyNameTuple(file.File.SourceLineNumbers); assemblyNameRow.Component_ = file.File.Component_; diff --git a/src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs b/src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs deleted file mode 100644 index 4157f23a..00000000 --- a/src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Clr.Interop -{ - using System; - using System.Runtime.InteropServices; - - /// - /// Interop class for mscorwks.dll assembly name APIs. - /// - internal sealed class ClrInterop - { - private static readonly Guid referenceIdentityGuid = new Guid("6eaf5ace-7917-4f3c-b129-e046a9704766"); - - /// - /// Protect the constructor. - /// - private ClrInterop() - { - } - - /// - /// Represents a reference to the unique signature of a code object. - /// - [ComImport] - [Guid("6eaf5ace-7917-4f3c-b129-e046a9704766")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IReferenceIdentity - { - /// - /// Get an assembly attribute. - /// - /// Attribute namespace. - /// Attribute name. - /// The assembly attribute. - [return: MarshalAs(UnmanagedType.LPWStr)] - string GetAttribute( - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName); - - /// - /// Set an assembly attribute. - /// - /// Attribute namespace. - /// Attribute name. - /// Attribute value. - void SetAttribute( - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName, - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeValue); - - /// - /// Get an iterator for the assembly's attributes. - /// - /// Assembly attribute enumerator. - IEnumIDENTITY_ATTRIBUTE EnumAttributes(); - - /// - /// Clone an IReferenceIdentity. - /// - /// Count of deltas. - /// The deltas. - /// Cloned IReferenceIdentity. - IReferenceIdentity Clone( - [In] IntPtr /*SIZE_T*/ countOfDeltas, - [In, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] deltas); - } - - /// - /// IEnumIDENTITY_ATTRIBUTE interface. - /// - [ComImport] - [Guid("9cdaae75-246e-4b00-a26d-b9aec137a3eb")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IEnumIDENTITY_ATTRIBUTE - { - /// - /// Gets the next attributes. - /// - /// Count of elements. - /// Array of attributes being returned. - /// The next attribute. - uint Next( - [In] uint celt, - [Out, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] attributes); - - /// - /// Copy the current attribute into a buffer. - /// - /// Number of available bytes. - /// Buffer into which attribute should be written. - /// Pointer to buffer containing the attribute. - IntPtr CurrentIntoBuffer( - [In] IntPtr /*SIZE_T*/ available, - [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data); - - /// - /// Skip past a number of elements. - /// - /// Count of elements to skip. - void Skip([In] uint celt); - - /// - /// Reset the enumeration to the beginning. - /// - void Reset(); - - /// - /// Clone this attribute enumeration. - /// - /// Clone of a IEnumIDENTITY_ATTRIBUTE. - IEnumIDENTITY_ATTRIBUTE Clone(); - } - - /// - /// Gets the guid. - /// - public static Guid ReferenceIdentityGuid - { - get { return referenceIdentityGuid; } - } - - /// - /// Gets an interface pointer to an object with the specified IID, in the assembly at the specified file path. - /// - /// A valid path to the requested assembly. - /// The IID of the interface to return. - /// The returned interface pointer. - /// The error code. - [DllImport("mscorwks.dll", CharSet = CharSet.Unicode, EntryPoint = "GetAssemblyIdentityFromFile")] - internal static extern uint GetAssemblyIdentityFromFile(System.String wszAssemblyPath, ref Guid riid, out IReferenceIdentity i); - - /// - /// Assembly attributes. Contains data about an IReferenceIdentity. - /// - [StructLayout(LayoutKind.Sequential)] - internal struct IDENTITY_ATTRIBUTE - { - [MarshalAs(UnmanagedType.LPWStr)] - public string AttributeNamespace; - [MarshalAs(UnmanagedType.LPWStr)] - public string AttributeName; - [MarshalAs(UnmanagedType.LPWStr)] - public string AttributeValue; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj index a08fde61..c251b8dd 100644 --- a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj +++ b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj @@ -23,6 +23,10 @@ + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 0efe5635..76f57676 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -409,7 +409,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Assembly information not getting gathered yet.")] + [Fact] public void CanBuildWithAssembly() { var folder = TestData.Get(@"TestData\Assembly"); @@ -436,7 +436,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\AssemblyMsiPackage\candle.exe"))); - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"test.wir")); + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); var section = intermediate.Sections.Single(); var wixFile = section.Tuples.OfType().Single(); @@ -444,7 +444,25 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(@"candle.exe", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); var msiAssemblyNameTuples = section.Tuples.OfType(); - Assert.NotEmpty(msiAssemblyNameTuples); + Assert.Equal(new[] + { + "culture", + "fileVersion", + "name", + "processorArchitecture", + "publicKeyToken", + "version" + }, msiAssemblyNameTuples.OrderBy(a => a.Name).Select(a => a.Name).ToArray()); + + Assert.Equal(new[] + { + "neutral", + "3.11.11810.0", + "candle", + "x86", + "256B3414DFA97718", + "3.0.0.0" + }, msiAssemblyNameTuples.OrderBy(a => a.Name).Select(a => a.Value).ToArray()); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 9cea0f4c..2a20fcff 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -1,4 +1,4 @@ - + -- cgit v1.2.3-55-g6feb