aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs5
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs3
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs10
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs505
-rw-r--r--src/WixToolset.Core/Linker.cs4
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs50
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msmbin0 -> 24576 bytes
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl11
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs24
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj3
12 files changed, 105 insertions, 513 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 32da410f..22858d1f 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -505,7 +505,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
505 // necessary. 505 // necessary.
506 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) 506 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
507 { 507 {
508 var sequenceTableName = sequence.ToString(); 508 var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString();
509 var sequenceTable = output.Tables[sequenceTableName]; 509 var sequenceTable = output.Tables[sequenceTableName];
510 510
511 if (null == sequenceTable) 511 if (null == sequenceTable)
@@ -519,8 +519,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
519 } 519 }
520 } 520 }
521 521
522 var command = new MergeModulesCommand(); 522 var command = new MergeModulesCommand(this.Messaging);
523 command.FileFacades = fileFacades; 523 command.FileFacades = fileFacades;
524 command.IntermediateFolder = this.IntermediateFolder;
524 command.Output = output; 525 command.Output = output;
525 command.OutputPath = this.OutputPath; 526 command.OutputPath = this.OutputPath;
526 command.SuppressedTableNames = suppressedTableNames; 527 command.SuppressedTableNames = suppressedTableNames;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
index 0cc5996a..9a609463 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
@@ -5,6 +5,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
5 using System; 5 using System;
6 using System.Collections; 6 using System.Collections;
7 using System.Globalization; 7 using System.Globalization;
8 using WixToolset.Core.Native;
8 using WixToolset.Core.WindowsInstaller.Msi; 9 using WixToolset.Core.WindowsInstaller.Msi;
9 10
10 /// <summary> 11 /// <summary>
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
index 49b6a6f8..62f7fce3 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
@@ -48,7 +48,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
48 { 48 {
49 var mergeModulesFileFacades = new List<FileFacade>(); 49 var mergeModulesFileFacades = new List<FileFacade>();
50 50
51 var merge = MsmInterop.GetMsmMerge(); 51 var interop = new MsmInterop();
52 var merge = interop.GetMsmMerge();
52 53
53 // Index all of the file rows to be able to detect collisions with files in the Merge Modules. 54 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
54 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions 55 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
index b90aecd1..cd6170d0 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
@@ -9,9 +9,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
9 using System.Runtime.InteropServices; 9 using System.Runtime.InteropServices;
10 using System.Text; 10 using System.Text;
11 using WixToolset.Core.Bind; 11 using WixToolset.Core.Bind;
12 using WixToolset.Core.Native;
12 using WixToolset.Core.WindowsInstaller.Msi; 13 using WixToolset.Core.WindowsInstaller.Msi;
13 using WixToolset.Data; 14 using WixToolset.Data;
14 using WixToolset.Data.Tuples;
15 using WixToolset.Data.WindowsInstaller; 15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Data.WindowsInstaller.Rows; 16 using WixToolset.Data.WindowsInstaller.Rows;
17 using WixToolset.Extensibility.Services; 17 using WixToolset.Extensibility.Services;
@@ -21,6 +21,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
21 /// </summary> 21 /// </summary>
22 internal class MergeModulesCommand 22 internal class MergeModulesCommand
23 { 23 {
24 public MergeModulesCommand(IMessaging messaging)
25 {
26 this.Messaging = messaging;
27 }
28
24 public IEnumerable<FileFacade> FileFacades { private get; set; } 29 public IEnumerable<FileFacade> FileFacades { private get; set; }
25 30
26 public IMessaging Messaging { private get; set; } 31 public IMessaging Messaging { private get; set; }
@@ -51,7 +56,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
51 string logPath = null; 56 string logPath = null;
52 try 57 try
53 { 58 {
54 merge = MsmInterop.GetMsmMerge(); 59 var interop = new MsmInterop();
60 merge = interop.GetMsmMerge();
55 61
56 logPath = Path.Combine(this.IntermediateFolder, "merge.log"); 62 logPath = Path.Combine(this.IntermediateFolder, "merge.log");
57 merge.OpenLog(logPath); 63 merge.OpenLog(logPath);
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
index ae872f45..5d18a230 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
@@ -74,7 +74,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
74 74
75 patchGroup.Add(facade); 75 patchGroup.Add(facade);
76 } 76 }
77 else 77 else if (!facade.FromModule)
78 { 78 {
79 var fileRow = fileRows.Get(facade.Id); 79 var fileRow = fileRows.Get(facade.Id);
80 fileRow.Sequence = ++lastSequence; 80 fileRow.Sequence = ++lastSequence;
diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs b/src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs
deleted file mode 100644
index 970d5aaa..00000000
--- a/src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs
+++ /dev/null
@@ -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
3namespace 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}
diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs
index 98232901..7b381347 100644
--- a/src/WixToolset.Core/Linker.cs
+++ b/src/WixToolset.Core/Linker.cs
@@ -356,7 +356,7 @@ namespace WixToolset.Core
356 case TupleDefinitionType.WixMerge: 356 case TupleDefinitionType.WixMerge:
357 if (SectionType.Product == resolvedSection.Type) 357 if (SectionType.Product == resolvedSection.Type)
358 { 358 {
359 this.ResolveFeatures(tuple, 0, 7, modulesToFeatures, null); 359 this.ResolveFeatures(tuple, -1, (int)WixMergeTupleFields.FeatureRef, modulesToFeatures, null);
360 } 360 }
361 break; 361 break;
362 362
@@ -1316,7 +1316,7 @@ namespace WixToolset.Core
1316 /// <param name="multipleFeatureComponents">Hashtable of known components under multiple features.</param> 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) 1317 private void ResolveFeatures(IntermediateTuple tuple, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents)
1318 { 1318 {
1319 var connectionId = tuple.AsString(connectionColumn); 1319 var connectionId = connectionColumn < 0 ? tuple.Id.Id : tuple.AsString(connectionColumn);
1320 var featureId = tuple.AsString(featureColumn); 1320 var featureId = tuple.AsString(featureColumn);
1321 1321
1322 if (EmptyGuid == featureId) 1322 if (EmptyGuid == featureId)
diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
index bb44395f..aa8a0a0d 100644
--- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
@@ -8,6 +8,9 @@ namespace WixToolsetTest.CoreIntegration
8 using Example.Extension; 8 using Example.Extension;
9 using WixBuildTools.TestSupport; 9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage; 10 using WixToolset.Core.TestPackage;
11 using WixToolset.Data;
12 using WixToolset.Data.Tuples;
13 using WixToolset.Data.WindowsInstaller;
11 using Xunit; 14 using Xunit;
12 15
13 public class MsiQueryFixture 16 public class MsiQueryFixture
@@ -1171,5 +1174,52 @@ namespace WixToolsetTest.CoreIntegration
1171 }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p)); 1174 }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p));
1172 } 1175 }
1173 } 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 }
1174 } 1224 }
1175} 1225}
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm
new file mode 100644
index 00000000..6f179aba
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm
Binary files differ
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl
new file mode 100644
index 00000000..38c12ac1
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This 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>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs
new file mode 100644
index 00000000..303e2ba8
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs
@@ -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>
diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
index 0651ec7a..9d2cf1d6 100644
--- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
+++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
@@ -77,6 +77,9 @@
77 <Content Include="TestData\SimpleBundle\data\fakeba.dll" CopyToOutputDirectory="PreserveNewest" /> 77 <Content Include="TestData\SimpleBundle\data\fakeba.dll" CopyToOutputDirectory="PreserveNewest" />
78 <Content Include="TestData\SimpleBundle\data\MsiPackage\Shared.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" /> 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" />
80 <Content Include="TestData\SimpleModule\data\test.txt" CopyToOutputDirectory="PreserveNewest" /> 83 <Content Include="TestData\SimpleModule\data\test.txt" CopyToOutputDirectory="PreserveNewest" />
81 <Content Include="TestData\SimpleModule\Module.en-us.wxl" CopyToOutputDirectory="PreserveNewest" /> 84 <Content Include="TestData\SimpleModule\Module.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
82 <Content Include="TestData\SimpleModule\Module.wxs" CopyToOutputDirectory="PreserveNewest" /> 85 <Content Include="TestData\SimpleModule\Module.wxs" CopyToOutputDirectory="PreserveNewest" />