@joebigelow / wix / commits / 6b30680f

Support merging merge modules

Rob Mensching committed May 22, 2020 at 14:53 UTC 6b30680fd7a712b45538c3f0a89d652f0457a893
12 files changed +105 -513
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+3 -2
@@ -505,7 +505,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
505 // necessary.
506 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
507 {
508 - var sequenceTableName = sequence.ToString();
508 + var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString();
509 var sequenceTable = output.Tables[sequenceTableName];
510
511 if (null == sequenceTable)
@@ -519,8 +519,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
519 }
520 }
521
522 - var command = new MergeModulesCommand();
522 + var command = new MergeModulesCommand(this.Messaging);
523 command.FileFacades = fileFacades;
524 + command.IntermediateFolder = this.IntermediateFolder;
525 command.Output = output;
526 command.OutputPath = this.OutputPath;
527 command.SuppressedTableNames = suppressedTableNames;
src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
+1
@@ -5,6 +5,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 + using WixToolset.Core.Native;
9 using WixToolset.Core.WindowsInstaller.Msi;
10
11 /// <summary>
src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+2 -1
@@ -48,7 +48,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
48 {
49 var mergeModulesFileFacades = new List<FileFacade>();
50
51 - var merge = MsmInterop.GetMsmMerge();
51 + var interop = new MsmInterop();
52 + var merge = interop.GetMsmMerge();
53
54 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
55 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+8 -2
@@ -9,9 +9,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
9 using System.Runtime.InteropServices;
10 using System.Text;
11 using WixToolset.Core.Bind;
12 + using WixToolset.Core.Native;
13 using WixToolset.Core.WindowsInstaller.Msi;
14 using WixToolset.Data;
14 - using WixToolset.Data.Tuples;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Data.WindowsInstaller.Rows;
17 using WixToolset.Extensibility.Services;
@@ -21,6 +21,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
21 /// </summary>
22 internal class MergeModulesCommand
23 {
24 + public MergeModulesCommand(IMessaging messaging)
25 + {
26 + this.Messaging = messaging;
27 + }
28 +
29 public IEnumerable<FileFacade> FileFacades { private get; set; }
30
31 public IMessaging Messaging { private get; set; }
@@ -51,7 +56,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
56 string logPath = null;
57 try
58 {
54 - merge = MsmInterop.GetMsmMerge();
59 + var interop = new MsmInterop();
60 + merge = interop.GetMsmMerge();
61
62 logPath = Path.Combine(this.IntermediateFolder, "merge.log");
63 merge.OpenLog(logPath);
src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+1 -1
@@ -74,7 +74,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
74
75 patchGroup.Add(facade);
76 }
77 - else
77 + else if (!facade.FromModule)
78 {
79 var fileRow = fileRows.Get(facade.Id);
80 fileRow.Sequence = ++lastSequence;
src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs deleted
-505
@@ -1,505 +0,0 @@
1 -// 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.
2 -
3 -namespace WixToolset.Core.WindowsInstaller.Msi
4 -{
5 - using System;
6 - using System.Runtime.InteropServices;
7 -
8 - /// <summary>
9 - /// Errors returned by merge operations.
10 - /// </summary>
11 - [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")]
12 - public enum MsmErrorType
13 - {
14 - /// <summary>
15 - /// A request was made to open a module with a language not supported by the module.
16 - /// No more general language is supported by the module.
17 - /// Adds msmErrorLanguageUnsupported to the Type property and the requested language
18 - /// to the Language Property (Error Object). All Error object properties are empty.
19 - /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT).
20 - /// </summary>
21 - msmErrorLanguageUnsupported = 1,
22 -
23 - /// <summary>
24 - /// A request was made to open a module with a supported language but the module has
25 - /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property
26 - /// and the applied transform's language to the Language Property of the Error object.
27 - /// This may not be the requested language if a more general language was used.
28 - /// All other properties of the Error object are empty. The OpenModule function
29 - /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT).
30 - /// </summary>
31 - msmErrorLanguageFailed = 2,
32 -
33 - /// <summary>
34 - /// The module cannot be merged because it excludes, or is excluded by, another module
35 - /// in the database. Adds msmErrorExclusion to the Type property of the Error object.
36 - /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the
37 - /// excluded module's row in the ModuleExclusion table. If an existing module excludes
38 - /// the module being merged, the excluded module's ModuleSignature information is added
39 - /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys
40 - /// contains the excluded module's ModuleSignature information. All other properties
41 - /// are empty (or -1).
42 - /// </summary>
43 - msmErrorExclusion = 3,
44 -
45 - /// <summary>
46 - /// Merge conflict during merge. The value of the Type property is set to
47 - /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain
48 - /// the table name and primary keys of the conflicting row in the database. The
49 - /// ModuleTable property and ModuleKeys property contain the table name and primary keys
50 - /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be
51 - /// null if the row does not exist in the database. For example, if the conflict is in a
52 - /// generated FeatureComponents table entry. On Windows Installer version 2.0, when
53 - /// merging a configurable merge module, configuration may cause these properties to
54 - /// refer to rows that do not exist in the module.
55 - /// </summary>
56 - msmErrorTableMerge = 4,
57 -
58 - /// <summary>
59 - /// There was a problem resequencing a sequence table to contain the necessary merged
60 - /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable
61 - /// and DatabaseKeys properties contain the sequence table name and primary keys
62 - /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties
63 - /// contain the sequence table name and primary key (action name) of the conflicting row.
64 - /// On Windows Installer version 2.0, when merging a configurable merge module,
65 - /// configuration may cause these properties to refer to rows that do not exist in the module.
66 - /// </summary>
67 - msmErrorResequenceMerge = 5,
68 -
69 - /// <summary>
70 - /// Not used.
71 - /// </summary>
72 - msmErrorFileCreate = 6,
73 -
74 - /// <summary>
75 - /// There was a problem creating a directory to extract a file to disk. The Path property
76 - /// contains the directory that could not be created. All other properties are empty or -1.
77 - /// Not available with Windows Installer version 1.0.
78 - /// </summary>
79 - msmErrorDirCreate = 7,
80 -
81 - /// <summary>
82 - /// A feature name is required to complete the merge, but no feature name was provided.
83 - /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys
84 - /// contain the table name and primary keys of the conflicting row. The ModuleTable and
85 - /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged.
86 - /// On Windows Installer version 2.0, when merging a configurable merge module, configuration
87 - /// may cause these properties to refer to rows that do not exist in the module.
88 - /// If the failure is in a generated FeatureComponents table, the DatabaseTable and
89 - /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to
90 - /// the row in the Component table causing the failure.
91 - /// </summary>
92 - msmErrorFeatureRequired = 8,
93 -
94 - /// <summary>
95 - /// Available with Window Installer version 2.0. Substitution of a Null value into a
96 - /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and
97 - /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row
98 - /// into the ModuleTable property and ModuleKeys property. All other properties of the Error
99 - /// object are set to an empty string or -1. This error causes the immediate failure of the
100 - /// merge and the MergeEx function to return E_FAIL.
101 - /// </summary>
102 - msmErrorBadNullSubstitution = 9,
103 -
104 - /// <summary>
105 - /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer
106 - /// Format Type into a Binary Type data column. This type of error returns
107 - /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the
108 - /// keys from the ModuleSubstitution table for this row into the ModuleTable property.
109 - /// All other properties of the Error object are set to an empty string or -1. This error
110 - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
111 - /// </summary>
112 - msmErrorBadSubstitutionType = 10,
113 -
114 - /// <summary>
115 - /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table
116 - /// references a configuration item not defined in the ModuleConfiguration table.
117 - /// This type of error returns msmErrorMissingConfigItem in the Type property and enters
118 - /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into
119 - /// the ModuleTable property. All other properties of the Error object are set to an empty
120 - /// string or -1. This error causes the immediate failure of the merge and the MergeEx
121 - /// function to return E_FAIL.
122 - /// </summary>
123 - msmErrorMissingConfigItem = 11,
124 -
125 - /// <summary>
126 - /// Available with Window Installer version 2.0. The authoring tool has returned a Null
127 - /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this
128 - /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution"
129 - /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property.
130 - /// All other properties of the Error object are set to an empty string or -1. This error
131 - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
132 - /// </summary>
133 - msmErrorBadNullResponse = 12,
134 -
135 - /// <summary>
136 - /// Available with Window Installer version 2.0. The authoring tool returned a failure code
137 - /// (not S_OK or S_FALSE) when asked for data. An error of this type will return
138 - /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution"
139 - /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property.
140 - /// All other properties of the Error object are set to an empty string or -1. This error
141 - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
142 - /// </summary>
143 - msmErrorDataRequestFailed = 13,
144 -
145 - /// <summary>
146 - /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was
147 - /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of
148 - /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of
149 - /// the error object are set to an empty string or -1. This error causes the immediate failure
150 - /// of the merge and causes the Merge function or MergeEx function to return E_FAIL.
151 - /// </summary>
152 - msmErrorPlatformMismatch = 14,
153 - }
154 -
155 - /// <summary>
156 - /// IMsmMerge2 interface.
157 - /// </summary>
158 - [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")]
159 - public interface IMsmMerge2
160 - {
161 - /// <summary>
162 - /// The OpenDatabase method of the Merge object opens a Windows Installer installation
163 - /// database, located at a specified path, that is to be merged with a module.
164 - /// </summary>
165 - /// <param name="path">Path to the database being opened.</param>
166 - void OpenDatabase(string path);
167 -
168 - /// <summary>
169 - /// The OpenModule method of the Merge object opens a Windows Installer merge module
170 - /// in read-only mode. A module must be opened before it can be merged with an installation database.
171 - /// </summary>
172 - /// <param name="fileName">Fully qualified file name pointing to a merge module.</param>
173 - /// <param name="language">A valid language identifier (LANGID).</param>
174 - void OpenModule(string fileName, short language);
175 -
176 - /// <summary>
177 - /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database.
178 - /// </summary>
179 - /// <param name="commit">true if changes should be saved, false otherwise.</param>
180 - void CloseDatabase(bool commit);
181 -
182 - /// <summary>
183 - /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module.
184 - /// </summary>
185 - void CloseModule();
186 -
187 - /// <summary>
188 - /// The OpenLog method of the Merge object opens a log file that receives progress and error messages.
189 - /// If the log file already exists, the installer appends new messages. If the log file does not exist,
190 - /// the installer creates a log file.
191 - /// </summary>
192 - /// <param name="fileName">Fully qualified filename pointing to a file to open or create.</param>
193 - void OpenLog(string fileName);
194 -
195 - /// <summary>
196 - /// The CloseLog method of the Merge object closes the current log file.
197 - /// </summary>
198 - void CloseLog();
199 -
200 - /// <summary>
201 - /// The Log method of the Merge object writes a text string to the currently open log file.
202 - /// </summary>
203 - /// <param name="message">The text string to display.</param>
204 - void Log(string message);
205 -
206 - /// <summary>
207 - /// Gets the errors from the last merge operation.
208 - /// </summary>
209 - /// <value>The errors from the last merge operation.</value>
210 - IMsmErrors Errors
211 - {
212 - get;
213 - }
214 -
215 - /// <summary>
216 - /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.
217 - /// </summary>
218 - /// <value>A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.</value>
219 - object Dependencies
220 - {
221 - get;
222 - }
223 -
224 - /// <summary>
225 - /// The Merge method of the Merge object executes a merge of the current database and current
226 - /// module. The merge attaches the components in the module to the feature identified by Feature.
227 - /// The root of the module's directory tree is redirected to the location given by RedirectDir.
228 - /// </summary>
229 - /// <param name="feature">The name of a feature in the database.</param>
230 - /// <param name="redirectDir">The key of an entry in the Directory table of the database.
231 - /// This parameter may be NULL or an empty string.</param>
232 - void Merge(string feature, string redirectDir);
233 -
234 - /// <summary>
235 - /// The Connect method of the Merge object connects a module to an additional feature.
236 - /// The module must have already been merged into the database or will be merged into the database.
237 - /// The feature must exist before calling this function.
238 - /// </summary>
239 - /// <param name="feature">The name of a feature already existing in the database.</param>
240 - void Connect(string feature);
241 -
242 - /// <summary>
243 - /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and
244 - /// saves it as the specified file. The installer creates this file if it does not already exist
245 - /// and overwritten if it does exist.
246 - /// </summary>
247 - /// <param name="fileName">The fully qualified destination file.</param>
248 - void ExtractCAB(string fileName);
249 -
250 - /// <summary>
251 - /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module
252 - /// and then writes those files to the destination directory.
253 - /// </summary>
254 - /// <param name="path">The fully qualified destination directory.</param>
255 - void ExtractFiles(string path);
256 -
257 - /// <summary>
258 - /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it
259 - /// takes an extra argument. The Merge method executes a merge of the current database and
260 - /// current module. The merge attaches the components in the module to the feature identified
261 - /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir.
262 - /// </summary>
263 - /// <param name="feature">The name of a feature in the database.</param>
264 - /// <param name="redirectDir">The key of an entry in the Directory table of the database. This parameter may
265 - /// be NULL or an empty string.</param>
266 - /// <param name="configuration">The pConfiguration argument is an interface implemented by the client. The argument may
267 - /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration
268 - /// functionality, but does not obligate the client to provide configuration data for any specific configurable item.</param>
269 - void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration);
270 -
271 - /// <summary>
272 - /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and
273 - /// then writes those files to the destination directory.
274 - /// </summary>
275 - /// <param name="path">The fully qualified destination directory.</param>
276 - /// <param name="longFileNames">Set to specify using long file names for path segments and final file names.</param>
277 - /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted.
278 - /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param>
279 - void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths);
280 -
281 - /// <summary>
282 - /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.
283 - /// </summary>
284 - /// <value>A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.</value>
285 - /// <remarks>Semantically, each interface in the enumerator represents an item that can be configured by the module consumer.
286 - /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum().
287 - /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics.</remarks>
288 - object ConfigurableItems
289 - {
290 - get;
291 - }
292 -
293 - /// <summary>
294 - /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to
295 - /// a source image on disk after a merge, taking into account changes to the module that might have been made
296 - /// during module configuration. The list of files to be extracted is taken from the file table of the module
297 - /// during the merge process. The list of files consists of every file successfully copied from the file table
298 - /// of the module to the target database. File table entries that were not copied due to primary key conflicts
299 - /// with existing rows in the database are not a part of this list. At image creation time, the directory for
300 - /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is
301 - /// the root of the source image for the install. fLongFileNames determines whether or not long file names are
302 - /// used for both path segments and final file names. The function fails if no database is open, no module is
303 - /// open, or no merge has been performed.
304 - /// </summary>
305 - /// <param name="path">The path of the root of the source image for the install.</param>
306 - /// <param name="longFileNames">Determines whether or not long file names are used for both path segments and final file names. </param>
307 - /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted.
308 - /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param>
309 - void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths);
310 -
311 - /// <summary>
312 - /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function
313 - /// returns the primary keys in the File table of the currently open module. The primary keys are returned
314 - /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles.
315 - /// </summary>
316 - IMsmStrings ModuleFiles
317 - {
318 - get;
319 - }
320 - }
321 -
322 - /// <summary>
323 - /// Collection of merge errors.
324 - /// </summary>
325 - [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")]
326 - public interface IMsmErrors
327 - {
328 - /// <summary>
329 - /// Gets the IMsmError at the specified index.
330 - /// </summary>
331 - /// <param name="index">The one-based index of the IMsmError to get.</param>
332 - IMsmError this[int index]
333 - {
334 - get;
335 - }
336 -
337 - /// <summary>
338 - /// Gets the count of IMsmErrors in this collection.
339 - /// </summary>
340 - /// <value>The count of IMsmErrors in this collection.</value>
341 - int Count
342 - {
343 - get;
344 - }
345 - }
346 -
347 - /// <summary>
348 - /// A merge error.
349 - /// </summary>
350 - [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")]
351 - public interface IMsmError
352 - {
353 - /// <summary>
354 - /// Gets the type of merge error.
355 - /// </summary>
356 - /// <value>The type of merge error.</value>
357 - MsmErrorType Type
358 - {
359 - get;
360 - }
361 -
362 - /// <summary>
363 - /// Gets the path information from the merge error.
364 - /// </summary>
365 - /// <value>The path information from the merge error.</value>
366 - string Path
367 - {
368 - get;
369 - }
370 -
371 - /// <summary>
372 - /// Gets the language information from the merge error.
373 - /// </summary>
374 - /// <value>The language information from the merge error.</value>
375 - short Language
376 - {
377 - get;
378 - }
379 -
380 - /// <summary>
381 - /// Gets the database table from the merge error.
382 - /// </summary>
383 - /// <value>The database table from the merge error.</value>
384 - string DatabaseTable
385 - {
386 - get;
387 - }
388 -
389 - /// <summary>
390 - /// Gets the collection of database keys from the merge error.
391 - /// </summary>
392 - /// <value>The collection of database keys from the merge error.</value>
393 - IMsmStrings DatabaseKeys
394 - {
395 - get;
396 - }
397 -
398 - /// <summary>
399 - /// Gets the module table from the merge error.
400 - /// </summary>
401 - /// <value>The module table from the merge error.</value>
402 - string ModuleTable
403 - {
404 - get;
405 - }
406 -
407 - /// <summary>
408 - /// Gets the collection of module keys from the merge error.
409 - /// </summary>
410 - /// <value>The collection of module keys from the merge error.</value>
411 - IMsmStrings ModuleKeys
412 - {
413 - get;
414 - }
415 - }
416 -
417 - /// <summary>
418 - /// A collection of strings.
419 - /// </summary>
420 - [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")]
421 - public interface IMsmStrings
422 - {
423 - /// <summary>
424 - /// Gets the string at the specified index.
425 - /// </summary>
426 - /// <param name="index">The one-based index of the string to get.</param>
427 - string this[int index]
428 - {
429 - get;
430 - }
431 -
432 - /// <summary>
433 - /// Gets the count of strings in this collection.
434 - /// </summary>
435 - /// <value>The count of strings in this collection.</value>
436 - int Count
437 - {
438 - get;
439 - }
440 - }
441 -
442 - /// <summary>
443 - /// Callback for configurable merge modules.
444 - /// </summary>
445 - [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
446 - public interface IMsmConfigureModule
447 - {
448 - /// <summary>
449 - /// Callback to retrieve text data for configurable merge modules.
450 - /// </summary>
451 - /// <param name="name">Name of the data to be retrieved.</param>
452 - /// <param name="configData">The data corresponding to the name.</param>
453 - /// <returns>The error code (HRESULT).</returns>
454 - [PreserveSig]
455 - int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData);
456 -
457 - /// <summary>
458 - /// Callback to retrieve integer data for configurable merge modules.
459 - /// </summary>
460 - /// <param name="name">Name of the data to be retrieved.</param>
461 - /// <param name="configData">The data corresponding to the name.</param>
462 - /// <returns>The error code (HRESULT).</returns>
463 - [PreserveSig]
464 - int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData);
465 - }
466 -
467 - /// <summary>
468 - /// Merge merge modules into an MSI file.
469 - /// </summary>
470 - [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")]
471 - public class MsmMerge2
472 - {
473 - }
474 -
475 - /// <summary>
476 - /// Defines the standard COM IClassFactory interface.
477 - /// </summary>
478 - [ComImport, Guid("00000001-0000-0000-C000-000000000046")]
479 - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
480 - public interface IClassFactory
481 - {
482 - [return:MarshalAs(UnmanagedType.IUnknown)]
483 - object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
484 - }
485 -
486 - /// <summary>
487 - /// Contains native methods for merge operations.
488 - /// </summary>
489 - public class MsmInterop
490 - {
491 - [DllImport("mergemod.dll", EntryPoint="DllGetClassObject", PreserveSig=false)]
492 - [return: MarshalAs(UnmanagedType.IUnknown)]
493 - private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
494 -
495 - /// <summary>
496 - /// Load the merge object directly from a local mergemod.dll without going through COM registration.
497 - /// </summary>
498 - /// <returns>Merge interface.</returns>
499 - public static IMsmMerge2 GetMsmMerge()
500 - {
501 - IClassFactory classFactory = (IClassFactory) MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID);
502 - return (IMsmMerge2) classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID);
503 - }
504 - }
505 -}
src/WixToolset.Core/Linker.cs
+2 -2
@@ -356,7 +356,7 @@ namespace WixToolset.Core
356 case TupleDefinitionType.WixMerge:
357 if (SectionType.Product == resolvedSection.Type)
358 {
359 - this.ResolveFeatures(tuple, 0, 7, modulesToFeatures, null);
359 + this.ResolveFeatures(tuple, -1, (int)WixMergeTupleFields.FeatureRef, modulesToFeatures, null);
360 }
361 break;
362
@@ -1316,7 +1316,7 @@ namespace WixToolset.Core
1316 /// <param name="multipleFeatureComponents">Hashtable of known components under multiple features.</param>
1317 private void ResolveFeatures(IntermediateTuple tuple, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents)
1318 {
1319 - var connectionId = tuple.AsString(connectionColumn);
1319 + var connectionId = connectionColumn < 0 ? tuple.Id.Id : tuple.AsString(connectionColumn);
1320 var featureId = tuple.AsString(featureColumn);
1321
1322 if (EmptyGuid == featureId)
src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
+50
@@ -8,6 +8,9 @@ namespace WixToolsetTest.CoreIntegration
8 using Example.Extension;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage;
11 + using WixToolset.Data;
12 + using WixToolset.Data.Tuples;
13 + using WixToolset.Data.WindowsInstaller;
14 using Xunit;
15
16 public class MsiQueryFixture
@@ -1171,5 +1174,52 @@ namespace WixToolsetTest.CoreIntegration
1174 }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p));
1175 }
1176 }
1177 +
1178 + [Fact]
1179 + public void CanMergeModule()
1180 + {
1181 + var folder = TestData.Get(@"TestData\SimpleMerge");
1182 +
1183 + using (var fs = new DisposableFileSystem())
1184 + {
1185 + var intermediateFolder = fs.GetFolder();
1186 + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi");
1187 + var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab");
1188 +
1189 + var result = WixRunner.Execute(new[]
1190 + {
1191 + "build",
1192 + Path.Combine(folder, "Package.wxs"),
1193 + "-loc", Path.Combine(folder, "Package.en-us.wxl"),
1194 + "-bindpath", Path.Combine(folder, ".data"),
1195 + "-intermediateFolder", intermediateFolder,
1196 + "-o", msiPath
1197 + });
1198 +
1199 + result.AssertSuccess();
1200 +
1201 + Assert.True(File.Exists(msiPath));
1202 + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
1203 +
1204 + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
1205 + var section = intermediate.Sections.Single();
1206 + Assert.Empty(section.Tuples.OfType<FileTuple>());
1207 +
1208 + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
1209 + Assert.Null(data.Tables["File"]);
1210 +
1211 + var results = Query.QueryDatabase(msiPath, new[] { "File" });
1212 + Assert.Equal(new[]
1213 + {
1214 + "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0"
1215 + }, results);
1216 +
1217 + var files = Query.GetCabinetFiles(cabPath);
1218 + Assert.Equal(new[]
1219 + {
1220 + "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE"
1221 + }, files.Select(f => f.Name).ToArray());
1222 + }
1223 + }
1224 }
1225 }
src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm
Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm differ
src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl new
+11
@@ -0,0 +1,11 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +
3 +<!--
4 +This file contains the declaration of all the localizable strings.
5 +-->
6 +<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7 +
8 + <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 + <String Id="FeatureTitle">MsiPackage</String>
10 +
11 +</WixLocalization>
src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs new
+24
@@ -0,0 +1,24 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Product Id="*" Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
4 + <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
5 +
6 + <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
7 + <MediaTemplate />
8 +
9 + <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
10 + <MergeRef Id="TestMsm" />
11 + </Feature>
12 + </Product>
13 +
14 + <Fragment>
15 + <Directory Id="TARGETDIR" Name="SourceDir">
16 + <Directory Id="ProgramFilesFolder">
17 + <Directory Id="INSTALLFOLDER" Name="MsiPackage">
18 + <!-- -->
19 + <Merge Id="TestMsm" Language="1033" SourceFile="test.msm" DiskId="1" />
20 + </Directory>
21 + </Directory>
22 + </Directory>
23 + </Fragment>
24 +</Wix>
src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
+3
@@ -77,6 +77,9 @@
77 <Content Include="TestData\SimpleBundle\data\fakeba.dll" CopyToOutputDirectory="PreserveNewest" />
78 <Content Include="TestData\SimpleBundle\data\MsiPackage\Shared.dll" CopyToOutputDirectory="PreserveNewest" />
79 <Content Include="TestData\SimpleBundle\data\MsiPackage\test.txt" CopyToOutputDirectory="PreserveNewest" />
80 + <Content Include="TestData\SimpleMerge\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
81 + <Content Include="TestData\SimpleMerge\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
82 + <Content Include="TestData\SimpleMerge\.data\test.msm" CopyToOutputDirectory="PreserveNewest" />
83 <Content Include="TestData\SimpleModule\data\test.txt" CopyToOutputDirectory="PreserveNewest" />
84 <Content Include="TestData\SimpleModule\Module.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
85 <Content Include="TestData\SimpleModule\Module.wxs" CopyToOutputDirectory="PreserveNewest" />