From e28fcc906046ed12a867b621fc0eabf75fd9db09 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 21 May 2020 23:08:52 -0700 Subject: Add support using mergmod.dll and its interfaces --- WixToolset.Core.Native.v3.ncrunchsolution | 6 + src/WixToolset.Core.Native/MsmInterop.cs | 507 +++++++++++++++++++++ .../WixToolset.Core.Native.csproj | 6 +- .../WixToolset.Core.Native.v3.ncrunchproject | 10 - src/test/WixToolsetTest.Core.Native/MsmFixture.cs | 18 + .../WixToolsetTest.Core.Native.csproj | 2 +- src/wixnative/Win32/mergemod.dll | Bin 0 -> 169304 bytes .../runtime.win.WixToolset.Core.Native.nuspec | 2 + src/wixnative/wixnative.vcxproj | 8 + src/wixnative/x64/mergemod.dll | Bin 0 -> 183848 bytes 10 files changed, 545 insertions(+), 14 deletions(-) create mode 100644 WixToolset.Core.Native.v3.ncrunchsolution create mode 100644 src/WixToolset.Core.Native/MsmInterop.cs delete mode 100644 src/WixToolset.Core.Native/WixToolset.Core.Native.v3.ncrunchproject create mode 100644 src/test/WixToolsetTest.Core.Native/MsmFixture.cs create mode 100644 src/wixnative/Win32/mergemod.dll create mode 100644 src/wixnative/x64/mergemod.dll diff --git a/WixToolset.Core.Native.v3.ncrunchsolution b/WixToolset.Core.Native.v3.ncrunchsolution new file mode 100644 index 00000000..10420ac9 --- /dev/null +++ b/WixToolset.Core.Native.v3.ncrunchsolution @@ -0,0 +1,6 @@ + + + True + True + + \ No newline at end of file diff --git a/src/WixToolset.Core.Native/MsmInterop.cs b/src/WixToolset.Core.Native/MsmInterop.cs new file mode 100644 index 00000000..6b1ad141 --- /dev/null +++ b/src/WixToolset.Core.Native/MsmInterop.cs @@ -0,0 +1,507 @@ +// 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.Native +{ + using System; + using System.IO; + using System.Reflection; + using System.Runtime.InteropServices; + + /// + /// Errors returned by merge operations. + /// + [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")] + public enum MsmErrorType + { + /// + /// A request was made to open a module with a language not supported by the module. + /// No more general language is supported by the module. + /// Adds msmErrorLanguageUnsupported to the Type property and the requested language + /// to the Language Property (Error Object). All Error object properties are empty. + /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). + /// + msmErrorLanguageUnsupported = 1, + + /// + /// A request was made to open a module with a supported language but the module has + /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property + /// and the applied transform's language to the Language Property of the Error object. + /// This may not be the requested language if a more general language was used. + /// All other properties of the Error object are empty. The OpenModule function + /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). + /// + msmErrorLanguageFailed = 2, + + /// + /// The module cannot be merged because it excludes, or is excluded by, another module + /// in the database. Adds msmErrorExclusion to the Type property of the Error object. + /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the + /// excluded module's row in the ModuleExclusion table. If an existing module excludes + /// the module being merged, the excluded module's ModuleSignature information is added + /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys + /// contains the excluded module's ModuleSignature information. All other properties + /// are empty (or -1). + /// + msmErrorExclusion = 3, + + /// + /// Merge conflict during merge. The value of the Type property is set to + /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain + /// the table name and primary keys of the conflicting row in the database. The + /// ModuleTable property and ModuleKeys property contain the table name and primary keys + /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be + /// null if the row does not exist in the database. For example, if the conflict is in a + /// generated FeatureComponents table entry. On Windows Installer version 2.0, when + /// merging a configurable merge module, configuration may cause these properties to + /// refer to rows that do not exist in the module. + /// + msmErrorTableMerge = 4, + + /// + /// There was a problem resequencing a sequence table to contain the necessary merged + /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable + /// and DatabaseKeys properties contain the sequence table name and primary keys + /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties + /// contain the sequence table name and primary key (action name) of the conflicting row. + /// On Windows Installer version 2.0, when merging a configurable merge module, + /// configuration may cause these properties to refer to rows that do not exist in the module. + /// + msmErrorResequenceMerge = 5, + + /// + /// Not used. + /// + msmErrorFileCreate = 6, + + /// + /// There was a problem creating a directory to extract a file to disk. The Path property + /// contains the directory that could not be created. All other properties are empty or -1. + /// Not available with Windows Installer version 1.0. + /// + msmErrorDirCreate = 7, + + /// + /// A feature name is required to complete the merge, but no feature name was provided. + /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys + /// contain the table name and primary keys of the conflicting row. The ModuleTable and + /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged. + /// On Windows Installer version 2.0, when merging a configurable merge module, configuration + /// may cause these properties to refer to rows that do not exist in the module. + /// If the failure is in a generated FeatureComponents table, the DatabaseTable and + /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to + /// the row in the Component table causing the failure. + /// + msmErrorFeatureRequired = 8, + + /// + /// Available with Window Installer version 2.0. Substitution of a Null value into a + /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and + /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row + /// into the ModuleTable property and ModuleKeys property. All other properties of the Error + /// object are set to an empty string or -1. This error causes the immediate failure of the + /// merge and the MergeEx function to return E_FAIL. + /// + msmErrorBadNullSubstitution = 9, + + /// + /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer + /// Format Type into a Binary Type data column. This type of error returns + /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the + /// keys from the ModuleSubstitution table for this row into the ModuleTable property. + /// All other properties of the Error object are set to an empty string or -1. This error + /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. + /// + msmErrorBadSubstitutionType = 10, + + /// + /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table + /// references a configuration item not defined in the ModuleConfiguration table. + /// This type of error returns msmErrorMissingConfigItem in the Type property and enters + /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into + /// the ModuleTable property. All other properties of the Error object are set to an empty + /// string or -1. This error causes the immediate failure of the merge and the MergeEx + /// function to return E_FAIL. + /// + msmErrorMissingConfigItem = 11, + + /// + /// Available with Window Installer version 2.0. The authoring tool has returned a Null + /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this + /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution" + /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property. + /// All other properties of the Error object are set to an empty string or -1. This error + /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. + /// + msmErrorBadNullResponse = 12, + + /// + /// Available with Window Installer version 2.0. The authoring tool returned a failure code + /// (not S_OK or S_FALSE) when asked for data. An error of this type will return + /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution" + /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property. + /// All other properties of the Error object are set to an empty string or -1. This error + /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. + /// + msmErrorDataRequestFailed = 13, + + /// + /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was + /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of + /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of + /// the error object are set to an empty string or -1. This error causes the immediate failure + /// of the merge and causes the Merge function or MergeEx function to return E_FAIL. + /// + msmErrorPlatformMismatch = 14, + } + + /// + /// IMsmMerge2 interface. + /// + [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")] + public interface IMsmMerge2 + { + /// + /// The OpenDatabase method of the Merge object opens a Windows Installer installation + /// database, located at a specified path, that is to be merged with a module. + /// + /// Path to the database being opened. + void OpenDatabase(string path); + + /// + /// The OpenModule method of the Merge object opens a Windows Installer merge module + /// in read-only mode. A module must be opened before it can be merged with an installation database. + /// + /// Fully qualified file name pointing to a merge module. + /// A valid language identifier (LANGID). + void OpenModule(string fileName, short language); + + /// + /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database. + /// + /// true if changes should be saved, false otherwise. + void CloseDatabase(bool commit); + + /// + /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module. + /// + void CloseModule(); + + /// + /// The OpenLog method of the Merge object opens a log file that receives progress and error messages. + /// If the log file already exists, the installer appends new messages. If the log file does not exist, + /// the installer creates a log file. + /// + /// Fully qualified filename pointing to a file to open or create. + void OpenLog(string fileName); + + /// + /// The CloseLog method of the Merge object closes the current log file. + /// + void CloseLog(); + + /// + /// The Log method of the Merge object writes a text string to the currently open log file. + /// + /// The text string to display. + void Log(string message); + + /// + /// Gets the errors from the last merge operation. + /// + /// The errors from the last merge operation. + IMsmErrors Errors + { + get; + } + + /// + /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. + /// + /// A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. + object Dependencies + { + get; + } + + /// + /// The Merge method of the Merge object executes a merge of the current database and current + /// module. The merge attaches the components in the module to the feature identified by Feature. + /// The root of the module's directory tree is redirected to the location given by RedirectDir. + /// + /// The name of a feature in the database. + /// The key of an entry in the Directory table of the database. + /// This parameter may be NULL or an empty string. + void Merge(string feature, string redirectDir); + + /// + /// The Connect method of the Merge object connects a module to an additional feature. + /// The module must have already been merged into the database or will be merged into the database. + /// The feature must exist before calling this function. + /// + /// The name of a feature already existing in the database. + void Connect(string feature); + + /// + /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and + /// saves it as the specified file. The installer creates this file if it does not already exist + /// and overwritten if it does exist. + /// + /// The fully qualified destination file. + void ExtractCAB(string fileName); + + /// + /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module + /// and then writes those files to the destination directory. + /// + /// The fully qualified destination directory. + void ExtractFiles(string path); + + /// + /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it + /// takes an extra argument. The Merge method executes a merge of the current database and + /// current module. The merge attaches the components in the module to the feature identified + /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir. + /// + /// The name of a feature in the database. + /// The key of an entry in the Directory table of the database. This parameter may + /// be NULL or an empty string. + /// The pConfiguration argument is an interface implemented by the client. The argument may + /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration + /// functionality, but does not obligate the client to provide configuration data for any specific configurable item. + void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration); + + /// + /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and + /// then writes those files to the destination directory. + /// + /// The fully qualified destination directory. + /// Set to specify using long file names for path segments and final file names. + /// This is a list of fully-qualified paths for the files that were successfully extracted. + /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. + void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths); + + /// + /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. + /// + /// A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. + /// Semantically, each interface in the enumerator represents an item that can be configured by the module consumer. + /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum(). + /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics. + object ConfigurableItems + { + get; + } + + /// + /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to + /// a source image on disk after a merge, taking into account changes to the module that might have been made + /// during module configuration. The list of files to be extracted is taken from the file table of the module + /// during the merge process. The list of files consists of every file successfully copied from the file table + /// of the module to the target database. File table entries that were not copied due to primary key conflicts + /// with existing rows in the database are not a part of this list. At image creation time, the directory for + /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is + /// the root of the source image for the install. fLongFileNames determines whether or not long file names are + /// used for both path segments and final file names. The function fails if no database is open, no module is + /// open, or no merge has been performed. + /// + /// The path of the root of the source image for the install. + /// Determines whether or not long file names are used for both path segments and final file names. + /// This is a list of fully-qualified paths for the files that were successfully extracted. + /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. + void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths); + + /// + /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function + /// returns the primary keys in the File table of the currently open module. The primary keys are returned + /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles. + /// + IMsmStrings ModuleFiles + { + get; + } + } + + /// + /// Collection of merge errors. + /// + [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")] + public interface IMsmErrors + { + /// + /// Gets the IMsmError at the specified index. + /// + /// The one-based index of the IMsmError to get. + IMsmError this[int index] + { + get; + } + + /// + /// Gets the count of IMsmErrors in this collection. + /// + /// The count of IMsmErrors in this collection. + int Count + { + get; + } + } + + /// + /// A merge error. + /// + [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")] + public interface IMsmError + { + /// + /// Gets the type of merge error. + /// + /// The type of merge error. + MsmErrorType Type + { + get; + } + + /// + /// Gets the path information from the merge error. + /// + /// The path information from the merge error. + string Path + { + get; + } + + /// + /// Gets the language information from the merge error. + /// + /// The language information from the merge error. + short Language + { + get; + } + + /// + /// Gets the database table from the merge error. + /// + /// The database table from the merge error. + string DatabaseTable + { + get; + } + + /// + /// Gets the collection of database keys from the merge error. + /// + /// The collection of database keys from the merge error. + IMsmStrings DatabaseKeys + { + get; + } + + /// + /// Gets the module table from the merge error. + /// + /// The module table from the merge error. + string ModuleTable + { + get; + } + + /// + /// Gets the collection of module keys from the merge error. + /// + /// The collection of module keys from the merge error. + IMsmStrings ModuleKeys + { + get; + } + } + + /// + /// A collection of strings. + /// + [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")] + public interface IMsmStrings + { + /// + /// Gets the string at the specified index. + /// + /// The one-based index of the string to get. + string this[int index] + { + get; + } + + /// + /// Gets the count of strings in this collection. + /// + /// The count of strings in this collection. + int Count + { + get; + } + } + + /// + /// Callback for configurable merge modules. + /// + [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] + public interface IMsmConfigureModule + { + /// + /// Callback to retrieve text data for configurable merge modules. + /// + /// Name of the data to be retrieved. + /// The data corresponding to the name. + /// The error code (HRESULT). + [PreserveSig] + int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData); + + /// + /// Callback to retrieve integer data for configurable merge modules. + /// + /// Name of the data to be retrieved. + /// The data corresponding to the name. + /// The error code (HRESULT). + [PreserveSig] + int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData); + } + + /// + /// Merge merge modules into an MSI file. + /// + [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")] + public class MsmMerge2 + { + } + + /// + /// Defines the standard COM IClassFactory interface. + /// + [ComImport, Guid("00000001-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IClassFactory + { + [return: MarshalAs(UnmanagedType.IUnknown)] + object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); + } + + /// + /// Contains native methods for merge operations. + /// + public class MsmInterop + { + [DllImport("mergemod.dll", EntryPoint = "DllGetClassObject", PreserveSig = false)] + [return: MarshalAs(UnmanagedType.IUnknown)] + private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); + + /// + /// Load the merge object directly from a local mergemod.dll without going through COM registration. + /// + /// Merge interface. + public IMsmMerge2 GetMsmMerge() + { + var classFactory = (IClassFactory)MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID); + return (IMsmMerge2)classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID); + } + } +} diff --git a/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj b/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj index ee5cbc8e..ca8797a9 100644 --- a/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj +++ b/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj @@ -23,13 +23,13 @@ - + - + - + diff --git a/src/WixToolset.Core.Native/WixToolset.Core.Native.v3.ncrunchproject b/src/WixToolset.Core.Native/WixToolset.Core.Native.v3.ncrunchproject deleted file mode 100644 index 0da1f42d..00000000 --- a/src/WixToolset.Core.Native/WixToolset.Core.Native.v3.ncrunchproject +++ /dev/null @@ -1,10 +0,0 @@ - - - - ..\..\build\Debug\Win32\wixnative.x86.exe - ..\..\build\Debug\Win32\wixnative.x86.pdb - ..\..\build\Debug\x64\wixnative.amd64.exe - ..\..\build\Debug\x64\wixnative.amd64.pdb - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.Core.Native/MsmFixture.cs b/src/test/WixToolsetTest.Core.Native/MsmFixture.cs new file mode 100644 index 00000000..a1e42d94 --- /dev/null +++ b/src/test/WixToolsetTest.Core.Native/MsmFixture.cs @@ -0,0 +1,18 @@ +// 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 WixToolsetTest.CoreNative +{ + using WixToolset.Core.Native; + using Xunit; + + public class MsmFixture + { + [Fact] + public void CanCreateMsmInterface() + { + var msm = new MsmInterop(); + var merge = msm.GetMsmMerge(); + Assert.NotNull(merge); + } + } +} diff --git a/src/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj b/src/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj index 263712bb..14928112 100644 --- a/src/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj +++ b/src/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj @@ -3,7 +3,7 @@ - netcoreapp2.0 + netcoreapp2.1 false diff --git a/src/wixnative/Win32/mergemod.dll b/src/wixnative/Win32/mergemod.dll new file mode 100644 index 00000000..2bf647b2 Binary files /dev/null and b/src/wixnative/Win32/mergemod.dll differ diff --git a/src/wixnative/runtime.win.WixToolset.Core.Native.nuspec b/src/wixnative/runtime.win.WixToolset.Core.Native.nuspec index 59eff4e6..a775b570 100644 --- a/src/wixnative/runtime.win.WixToolset.Core.Native.nuspec +++ b/src/wixnative/runtime.win.WixToolset.Core.Native.nuspec @@ -14,8 +14,10 @@ + + diff --git a/src/wixnative/wixnative.vcxproj b/src/wixnative/wixnative.vcxproj index 57afcc0b..80e0aae5 100644 --- a/src/wixnative/wixnative.vcxproj +++ b/src/wixnative/wixnative.vcxproj @@ -69,6 +69,14 @@ + + + true + %(Filename)%(Extension) + PreserveNewest + + + diff --git a/src/wixnative/x64/mergemod.dll b/src/wixnative/x64/mergemod.dll new file mode 100644 index 00000000..2f4cdc7c Binary files /dev/null and b/src/wixnative/x64/mergemod.dll differ -- cgit v1.2.3-55-g6feb