aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
committerRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
commitdbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch)
tree0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core.WindowsInstaller/Bind
parentfbf986eb97f68396797a89fc7d40dec07b775440 (diff)
downloadwix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs314
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs1282
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs135
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs470
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs177
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs122
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs79
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs91
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs606
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs499
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs87
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs68
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs226
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs332
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs149
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs351
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs118
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs80
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs533
19 files changed, 5719 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
new file mode 100644
index 00000000..23c481b7
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
@@ -0,0 +1,314 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using WixToolset.Core.Bind;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 /// <summary>
13 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
14 /// </summary>
15 public class AssignMediaCommand
16 {
17 public AssignMediaCommand()
18 {
19 this.CabinetNameTemplate = "Cab{0}.cab";
20 }
21
22 public Output Output { private get; set; }
23
24 public bool FilesCompressed { private get; set; }
25
26 public string CabinetNameTemplate { private get; set; }
27
28 public IEnumerable<FileFacade> FileFacades { private get; set; }
29
30 public TableDefinitionCollection TableDefinitions { private get; set; }
31
32 /// <summary>
33 /// Gets cabinets with their file rows.
34 /// </summary>
35 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
36
37 /// <summary>
38 /// Get media rows.
39 /// </summary>
40 public RowDictionary<MediaRow> MediaRows { get; private set; }
41
42 /// <summary>
43 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
44 /// This contains all the files when Package element is marked with compression=no
45 /// </summary>
46 public IEnumerable<FileFacade> UncompressedFileFacades { get; private set; }
47
48 public void Execute()
49 {
50 Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia = new Dictionary<MediaRow, List<FileFacade>>();
51
52 RowDictionary<MediaRow> mediaRows = new RowDictionary<MediaRow>();
53
54 List<FileFacade> uncompressedFiles = new List<FileFacade>();
55
56 MediaRow mergeModuleMediaRow = null;
57 Table mediaTable = this.Output.Tables["Media"];
58 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
59
60 // If both tables are authored, it is an error.
61 if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1))
62 {
63 throw new WixException(WixErrors.MediaTableCollision(null));
64 }
65
66 // When building merge module, all the files go to "#MergeModule.CABinet".
67 if (OutputType.Module == this.Output.Type)
68 {
69 Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]);
70 mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null);
71 mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet";
72
73 filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>());
74 }
75
76 if (OutputType.Module == this.Output.Type || null == mediaTemplateTable)
77 {
78 this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
79 }
80 else
81 {
82 this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
83 }
84
85 this.FileFacadesByCabinetMedia = new Dictionary<MediaRow, IEnumerable<FileFacade>>();
86
87 foreach (var mediaRowWithFiles in filesByCabinetMedia)
88 {
89 this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value);
90 }
91
92 this.MediaRows = mediaRows;
93
94 this.UncompressedFileFacades = uncompressedFiles;
95 }
96
97 /// <summary>
98 /// Assign files to cabinets based on MediaTemplate authoring.
99 /// </summary>
100 /// <param name="fileFacades">FileRowCollection</param>
101 private void AutoAssignFiles(Table mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
102 {
103 const int MaxCabIndex = 999;
104
105 ulong currentPreCabSize = 0;
106 ulong maxPreCabSizeInBytes;
107 int maxPreCabSizeInMB = 0;
108 int currentCabIndex = 0;
109
110 MediaRow currentMediaRow = null;
111
112 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
113
114 // Auto assign files to cabinets based on maximum uncompressed media size
115 mediaTable.Rows.Clear();
116 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
117
118 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
119 {
120 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
121 }
122
123 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
124
125 try
126 {
127 // Override authored mums value if environment variable is authored.
128 if (!String.IsNullOrEmpty(mumsString))
129 {
130 maxPreCabSizeInMB = Int32.Parse(mumsString);
131 }
132 else
133 {
134 maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
135 }
136
137 maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024;
138 }
139 catch (FormatException)
140 {
141 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
142 }
143 catch (OverflowException)
144 {
145 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
146 }
147
148 foreach (FileFacade facade in this.FileFacades)
149 {
150 // When building a product, if the current file is not to be compressed or if
151 // the package set not to be compressed, don't cab it.
152 if (OutputType.Product == this.Output.Type &&
153 (YesNoType.No == facade.File.Compressed ||
154 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
155 {
156 uncompressedFiles.Add(facade);
157 continue;
158 }
159
160 if (currentCabIndex == MaxCabIndex)
161 {
162 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
163 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
164 facade.WixFile.DiskId = currentCabIndex;
165 cabinetFiles.Add(facade);
166 continue;
167 }
168
169 // Update current cab size.
170 currentPreCabSize += (ulong)facade.File.FileSize;
171
172 if (currentPreCabSize > maxPreCabSizeInBytes)
173 {
174 // Overflow due to current file
175 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
176 mediaRows.Add(currentMediaRow);
177 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
178
179 List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow];
180 facade.WixFile.DiskId = currentCabIndex;
181 cabinetFileRows.Add(facade);
182 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
183 currentPreCabSize = (ulong)facade.File.FileSize;
184 }
185 else
186 {
187 // File fits in the current cab.
188 if (currentMediaRow == null)
189 {
190 // Create new cab and MediaRow
191 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
192 mediaRows.Add(currentMediaRow);
193 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
194 }
195
196 // Associate current file with current cab.
197 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
198 facade.WixFile.DiskId = currentCabIndex;
199 cabinetFiles.Add(facade);
200 }
201 }
202
203 // If there are uncompressed files and no MediaRow, create a default one.
204 if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0)
205 {
206 MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null);
207 defaultMediaRow.DiskId = 1;
208 mediaRows.Add(defaultMediaRow);
209 }
210 }
211
212 /// <summary>
213 /// Assign files to cabinets based on Media authoring.
214 /// </summary>
215 /// <param name="mediaTable"></param>
216 /// <param name="mergeModuleMediaRow"></param>
217 /// <param name="fileFacades"></param>
218 private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
219 {
220 if (OutputType.Module != this.Output.Type)
221 {
222 if (null != mediaTable)
223 {
224 Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase);
225 foreach (MediaRow mediaRow in mediaTable.Rows)
226 {
227 // If the Media row has a cabinet, make sure it is unique across all Media rows.
228 if (!String.IsNullOrEmpty(mediaRow.Cabinet))
229 {
230 MediaRow existingRow;
231 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow))
232 {
233 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet));
234 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
235 }
236 else
237 {
238 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
239 }
240 }
241
242 mediaRows.Add(mediaRow);
243 }
244 }
245
246 foreach (MediaRow mediaRow in mediaRows.Values)
247 {
248 if (null != mediaRow.Cabinet)
249 {
250 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
251 }
252 }
253 }
254
255 foreach (FileFacade facade in fileFacades)
256 {
257 if (OutputType.Module == this.Output.Type)
258 {
259 filesByCabinetMedia[mergeModuleMediaRow].Add(facade);
260 }
261 else
262 {
263 MediaRow mediaRow;
264 if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow))
265 {
266 Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId));
267 continue;
268 }
269
270 // When building a product, if the current file is not to be compressed or if
271 // the package set not to be compressed, don't cab it.
272 if (OutputType.Product == this.Output.Type &&
273 (YesNoType.No == facade.File.Compressed ||
274 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
275 {
276 uncompressedFiles.Add(facade);
277 }
278 else // file is marked compressed.
279 {
280 List<FileFacade> cabinetFiles;
281 if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles))
282 {
283 cabinetFiles.Add(facade);
284 }
285 else
286 {
287 Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId));
288 }
289 }
290 }
291 }
292 }
293
294 /// <summary>
295 /// Adds a row to the media table with cab name template filled in.
296 /// </summary>
297 /// <param name="mediaTable"></param>
298 /// <param name="cabIndex"></param>
299 /// <returns></returns>
300 private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex)
301 {
302 MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
303 currentMediaRow.DiskId = cabIndex;
304 currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex);
305
306 Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]);
307 WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
308 row.DiskId = cabIndex;
309 row.CompressionLevel = mediaTemplateRow.CompressionLevel;
310
311 return currentMediaRow;
312 }
313 }
314}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
new file mode 100644
index 00000000..2e2c5417
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -0,0 +1,1282 @@
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.Bind
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using WixToolset.Bind;
13 using WixToolset.Core.Bind;
14 using WixToolset.Core.WindowsInstaller.Databases;
15 using WixToolset.Data;
16 using WixToolset.Data.Bind;
17 using WixToolset.Data.Rows;
18 using WixToolset.Extensibility;
19 using WixToolset.Msi;
20
21 /// <summary>
22 /// Binds a databse.
23 /// </summary>
24 internal class BindDatabaseCommand
25 {
26 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
27 private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
28
29 public BindDatabaseCommand(IBindContext context, Validator validator)
30 {
31 this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions();
32
33 this.BindPaths = context.BindPaths;
34 this.CabbingThreadCount = context.CabbingThreadCount;
35 this.CabCachePath = context.CabCachePath;
36 this.Codepage = context.Codepage;
37 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
38 this.DelayedFields = context.DelayedFields;
39 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
40 this.Extensions = context.Extensions;
41 this.Output = context.IntermediateRepresentation;
42 this.OutputPath = context.OutputPath;
43 this.PdbFile = context.OutputPdbPath;
44 this.IntermediateFolder = context.IntermediateFolder;
45 this.Validator = validator;
46 this.WixVariableResolver = context.WixVariableResolver;
47
48 this.BackendExtensions = context.ExtensionManager.Create<IWindowsInstallerBackendExtension>();
49 }
50
51 private IEnumerable<BindPath> BindPaths { get; }
52
53 private int Codepage { get; }
54
55 private int CabbingThreadCount { get; }
56
57 private string CabCachePath { get; }
58
59 private CompressionLevel DefaultCompressionLevel { get; }
60
61 public IEnumerable<IDelayedField> DelayedFields { get; }
62
63 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; }
64
65 public bool DeltaBinaryPatch { get; set; }
66
67 private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; }
68
69 private IEnumerable<IBinderExtension> Extensions { get; }
70
71 private IEnumerable<InspectorExtension> InspectorExtensions { get; }
72
73 private string PdbFile { get; }
74
75 private Output Output { get; }
76
77 private string OutputPath { get; }
78
79 private bool SuppressAddingValidationRows { get; }
80
81 private bool SuppressLayout { get; }
82
83 private TableDefinitionCollection TableDefinitions { get; }
84
85 private string IntermediateFolder { get; }
86
87 private Validator Validator { get; }
88
89 private IBindVariableResolver WixVariableResolver { get; }
90
91 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
92
93 public IEnumerable<string> ContentFilePaths { get; private set; }
94
95 public void Execute()
96 {
97 List<FileTransfer> fileTransfers = new List<FileTransfer>();
98
99 HashSet<string> suppressedTableNames = new HashSet<string>();
100
101 // If there are any fields to resolve later, create the cache to populate during bind.
102 IDictionary<string, string> variableCache = null;
103 if (this.DelayedFields.Any())
104 {
105 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
106 }
107
108 this.LocalizeUI(this.Output.Tables);
109
110 // Process the summary information table before the other tables.
111 bool compressed;
112 bool longNames;
113 int installerVersion;
114 string modularizationGuid;
115 {
116 BindSummaryInfoCommand command = new BindSummaryInfoCommand();
117 command.Output = this.Output;
118 command.Execute();
119
120 compressed = command.Compressed;
121 longNames = command.LongNames;
122 installerVersion = command.InstallerVersion;
123 modularizationGuid = command.ModularizationGuid;
124 }
125
126 // Stop processing if an error previously occurred.
127 if (Messaging.Instance.EncounteredError)
128 {
129 return;
130 }
131
132 // Modularize identifiers and add tables with real streams to the import tables.
133 if (OutputType.Module == this.Output.Type)
134 {
135 // Gather all the suppress modularization identifiers
136 HashSet<string> suppressModularizationIdentifiers = null;
137 Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"];
138 if (null != wixSuppressModularizationTable)
139 {
140 suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0]));
141 }
142
143 foreach (Table table in this.Output.Tables)
144 {
145 table.Modularize(modularizationGuid, suppressModularizationIdentifiers);
146 }
147 }
148
149 // This must occur after all variables and source paths have been resolved and after modularization.
150 List<FileFacade> fileFacades;
151 {
152 GetFileFacadesCommand command = new GetFileFacadesCommand();
153 command.FileTable = this.Output.Tables["File"];
154 command.WixFileTable = this.Output.Tables["WixFile"];
155 command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"];
156 command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"];
157 command.Execute();
158
159 fileFacades = command.FileFacades;
160 }
161
162 ////if (OutputType.Patch == this.Output.Type)
163 ////{
164 //// foreach (SubStorage substorage in this.Output.SubStorages)
165 //// {
166 //// Output transform = substorage.Data;
167
168 //// ResolveFieldsCommand command = new ResolveFieldsCommand();
169 //// command.Tables = transform.Tables;
170 //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
171 //// command.FileManagerCore = this.FileManagerCore;
172 //// command.FileManagers = this.FileManagers;
173 //// command.SupportDelayedResolution = false;
174 //// command.TempFilesLocation = this.TempFilesLocation;
175 //// command.WixVariableResolver = this.WixVariableResolver;
176 //// command.Execute();
177
178 //// this.MergeUnrealTables(transform.Tables);
179 //// }
180 ////}
181
182 {
183 CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand();
184 command.PropertyTable = this.Output.Tables["Property"];
185 command.WixPropertyTable = this.Output.Tables["WixProperty"];
186 command.Execute();
187 }
188
189 if (Messaging.Instance.EncounteredError)
190 {
191 return;
192 }
193
194 // Add binder variables for all properties.
195 Table propertyTable = this.Output.Tables["Property"];
196 if (null != propertyTable)
197 {
198 foreach (PropertyRow propertyRow in propertyTable.Rows)
199 {
200 // Set the ProductCode if it is to be generated.
201 if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal))
202 {
203 propertyRow.Value = Common.GenerateGuid();
204
205 // Update the target ProductCode in any instance transforms.
206 foreach (SubStorage subStorage in this.Output.SubStorages)
207 {
208 Output subStorageOutput = subStorage.Data;
209 if (OutputType.Transform != subStorageOutput.Type)
210 {
211 continue;
212 }
213
214 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"];
215 foreach (Row row in instanceSummaryInformationTable.Rows)
216 {
217 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0))
218 {
219 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
220 break;
221 }
222 }
223 }
224 }
225
226 // Add the property name and value to the variableCache.
227 if (null != variableCache)
228 {
229 string key = String.Concat("property.", Common.Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property));
230 variableCache[key] = propertyRow.Value;
231 }
232 }
233 }
234
235 // Extract files that come from cabinet files (this does not extract files from merge modules).
236 {
237 ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand();
238 command.FilesWithEmbeddedFiles = this.ExpectedEmbeddedFiles;
239 command.Execute();
240 }
241
242 if (OutputType.Product == this.Output.Type)
243 {
244 // Retrieve files and their information from merge modules.
245 Table wixMergeTable = this.Output.Tables["WixMerge"];
246
247 if (null != wixMergeTable)
248 {
249 ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand();
250 command.FileFacades = fileFacades;
251 command.FileTable = this.Output.Tables["File"];
252 command.WixFileTable = this.Output.Tables["WixFile"];
253 command.WixMergeTable = wixMergeTable;
254 command.OutputInstallerVersion = installerVersion;
255 command.SuppressLayout = this.SuppressLayout;
256 command.TempFilesLocation = this.IntermediateFolder;
257 command.Execute();
258
259 fileFacades.AddRange(command.MergeModulesFileFacades);
260 }
261 }
262 else if (OutputType.Patch == this.Output.Type)
263 {
264 // Merge transform data into the output object.
265 IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
266
267 fileFacades.AddRange(filesFromTransform);
268 }
269
270 // stop processing if an error previously occurred
271 if (Messaging.Instance.EncounteredError)
272 {
273 return;
274 }
275
276 Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation());
277
278 // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table).
279 {
280 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
281 command.FileFacades = fileFacades;
282 command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule);
283 command.ModularizationGuid = modularizationGuid;
284 command.Output = this.Output;
285 command.OverwriteHash = true;
286 command.TableDefinitions = this.TableDefinitions;
287 command.VariableCache = variableCache;
288 command.Execute();
289 }
290
291 // Set generated component guids.
292 this.SetComponentGuids(this.Output);
293
294 // With the Component Guids set now we can create instance transforms.
295 this.CreateInstanceTransforms(this.Output);
296
297 this.ValidateComponentGuids(this.Output);
298
299 this.UpdateControlText(this.Output);
300
301 if (this.DelayedFields.Any())
302 {
303 ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand();
304 command.OutputType = this.Output.Type;
305 command.DelayedFields = this.DelayedFields;
306 command.ModularizationGuid = null;
307 command.VariableCache = variableCache;
308 command.Execute();
309 }
310
311 // Assign files to media.
312 RowDictionary<MediaRow> assignedMediaRows;
313 Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia;
314 IEnumerable<FileFacade> uncompressedFiles;
315 {
316 AssignMediaCommand command = new AssignMediaCommand();
317 command.FilesCompressed = compressed;
318 command.FileFacades = fileFacades;
319 command.Output = this.Output;
320 command.TableDefinitions = this.TableDefinitions;
321 command.Execute();
322
323 assignedMediaRows = command.MediaRows;
324 filesByCabinetMedia = command.FileFacadesByCabinetMedia;
325 uncompressedFiles = command.UncompressedFileFacades;
326 }
327
328 // Update file sequence.
329 this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows);
330
331 // stop processing if an error previously occurred
332 if (Messaging.Instance.EncounteredError)
333 {
334 return;
335 }
336
337 // Extended binder extensions can be called now that fields are resolved.
338 {
339 Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]);
340
341 foreach (IBinderExtension extension in this.Extensions)
342 {
343 extension.AfterResolvedFields(this.Output);
344 }
345
346 List<FileFacade> updatedFileFacades = new List<FileFacade>();
347
348 foreach (Row updatedFile in updatedFiles.Rows)
349 {
350 string updatedId = updatedFile.FieldAsString(0);
351
352 FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId));
353
354 updatedFileFacades.Add(updatedFacade);
355 }
356
357 if (updatedFileFacades.Any())
358 {
359 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
360 command.FileFacades = fileFacades;
361 command.UpdateFileFacades = updatedFileFacades;
362 command.ModularizationGuid = modularizationGuid;
363 command.Output = this.Output;
364 command.OverwriteHash = true;
365 command.TableDefinitions = this.TableDefinitions;
366 command.VariableCache = variableCache;
367 command.Execute();
368 }
369 }
370
371 // stop processing if an error previously occurred
372 if (Messaging.Instance.EncounteredError)
373 {
374 return;
375 }
376
377 Directory.CreateDirectory(this.IntermediateFolder);
378
379 if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch)
380 {
381 CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand();
382 command.FileFacades = fileFacades;
383 command.WixPatchIdTable = this.Output.Tables["WixPatchId"];
384 command.TempFilesLocation = this.IntermediateFolder;
385 command.Execute();
386 }
387
388 // create cabinet files and process uncompressed files
389 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
390 if (!this.SuppressLayout || OutputType.Module == this.Output.Type)
391 {
392 Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles());
393
394 var command = new CreateCabinetsCommand();
395 command.CabbingThreadCount = this.CabbingThreadCount;
396 command.CabCachePath = this.CabCachePath;
397 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
398 command.Output = this.Output;
399 command.BackendExtensions = this.BackendExtensions;
400 command.LayoutDirectory = layoutDirectory;
401 command.Compressed = compressed;
402 command.FileRowsByCabinet = filesByCabinetMedia;
403 command.ResolveMedia = this.ResolveMedia;
404 command.TableDefinitions = this.TableDefinitions;
405 command.TempFilesLocation = this.IntermediateFolder;
406 command.WixMediaTable = this.Output.Tables["WixMedia"];
407 command.Execute();
408
409 fileTransfers.AddRange(command.FileTransfers);
410 }
411
412 if (OutputType.Patch == this.Output.Type)
413 {
414 // copy output data back into the transforms
415 this.CopyToTransformData(this.Output);
416 }
417
418 // stop processing if an error previously occurred
419 if (Messaging.Instance.EncounteredError)
420 {
421 return;
422 }
423
424 // add back suppressed tables which must be present prior to merging in modules
425 if (OutputType.Product == this.Output.Type)
426 {
427 Table wixMergeTable = this.Output.Tables["WixMerge"];
428
429 if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count)
430 {
431 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
432 {
433 string sequenceTableName = sequence.ToString();
434 Table sequenceTable = this.Output.Tables[sequenceTableName];
435
436 if (null == sequenceTable)
437 {
438 sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]);
439 }
440
441 if (0 == sequenceTable.Rows.Count)
442 {
443 suppressedTableNames.Add(sequenceTableName);
444 }
445 }
446 }
447 }
448
449 //foreach (BinderExtension extension in this.Extensions)
450 //{
451 // extension.PostBind(this.Context);
452 //}
453
454 // generate database file
455 Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase());
456 string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath));
457 this.GenerateDatabase(this.Output, tempDatabaseFile, false, false);
458
459 FileTransfer transfer;
460 if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future
461 {
462 transfer.Built = true;
463 fileTransfers.Add(transfer);
464 }
465
466 // stop processing if an error previously occurred
467 if (Messaging.Instance.EncounteredError)
468 {
469 return;
470 }
471
472 // Output the output to a file
473 Pdb pdb = new Pdb();
474 pdb.Output = this.Output;
475 if (!String.IsNullOrEmpty(this.PdbFile))
476 {
477 pdb.Save(this.PdbFile);
478 }
479
480 // Merge modules.
481 if (OutputType.Product == this.Output.Type)
482 {
483 Messaging.Instance.OnMessage(WixVerboses.MergingModules());
484
485 MergeModulesCommand command = new MergeModulesCommand();
486 command.FileFacades = fileFacades;
487 command.Output = this.Output;
488 command.OutputPath = tempDatabaseFile;
489 command.SuppressedTableNames = suppressedTableNames;
490 command.Execute();
491
492 // stop processing if an error previously occurred
493 if (Messaging.Instance.EncounteredError)
494 {
495 return;
496 }
497 }
498
499 // inspect the MSI prior to running ICEs
500 //InspectorCore inspectorCore = new InspectorCore();
501 //foreach (InspectorExtension inspectorExtension in this.InspectorExtensions)
502 //{
503 // inspectorExtension.Core = inspectorCore;
504 // inspectorExtension.InspectDatabase(tempDatabaseFile, pdb);
505
506 // inspectorExtension.Core = null; // reset.
507 //}
508
509 if (Messaging.Instance.EncounteredError)
510 {
511 return;
512 }
513
514 // validate the output if there is an MSI validator
515 if (null != this.Validator)
516 {
517 Stopwatch stopwatch = Stopwatch.StartNew();
518
519 // set the output file for source line information
520 this.Validator.Output = this.Output;
521
522 Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase());
523
524 this.Validator.Validate(tempDatabaseFile);
525
526 stopwatch.Stop();
527 Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
528
529 // Stop processing if an error occurred.
530 if (Messaging.Instance.EncounteredError)
531 {
532 return;
533 }
534 }
535
536 // Process uncompressed files.
537 if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
538 {
539 var command = new ProcessUncompressedFilesCommand();
540 command.Compressed = compressed;
541 command.FileFacades = uncompressedFiles;
542 command.LayoutDirectory = layoutDirectory;
543 command.LongNamesInImage = longNames;
544 command.MediaRows = assignedMediaRows;
545 command.ResolveMedia = this.ResolveMedia;
546 command.DatabasePath = tempDatabaseFile;
547 command.WixMediaTable = this.Output.Tables["WixMedia"];
548 command.Execute();
549
550 fileTransfers.AddRange(command.FileTransfers);
551 }
552
553 this.FileTransfers = fileTransfers;
554 this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList();
555 }
556
557 /// <summary>
558 /// Localize dialogs and controls.
559 /// </summary>
560 /// <param name="tables">The tables to localize.</param>
561 private void LocalizeUI(TableIndexedCollection tables)
562 {
563 Table dialogTable = tables["Dialog"];
564 if (null != dialogTable)
565 {
566 foreach (Row row in dialogTable.Rows)
567 {
568 string dialog = (string)row[0];
569
570 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl))
571 {
572 if (CompilerConstants.IntegerNotSet != localizedControl.X)
573 {
574 row[1] = localizedControl.X;
575 }
576
577 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
578 {
579 row[2] = localizedControl.Y;
580 }
581
582 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
583 {
584 row[3] = localizedControl.Width;
585 }
586
587 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
588 {
589 row[4] = localizedControl.Height;
590 }
591
592 row[5] = (int)row[5] | localizedControl.Attributes;
593
594 if (!String.IsNullOrEmpty(localizedControl.Text))
595 {
596 row[6] = localizedControl.Text;
597 }
598 }
599 }
600 }
601
602 Table controlTable = tables["Control"];
603 if (null != controlTable)
604 {
605 foreach (Row row in controlTable.Rows)
606 {
607 string dialog = (string)row[0];
608 string control = (string)row[1];
609
610 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl))
611 {
612 if (CompilerConstants.IntegerNotSet != localizedControl.X)
613 {
614 row[3] = localizedControl.X.ToString();
615 }
616
617 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
618 {
619 row[4] = localizedControl.Y.ToString();
620 }
621
622 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
623 {
624 row[5] = localizedControl.Width.ToString();
625 }
626
627 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
628 {
629 row[6] = localizedControl.Height.ToString();
630 }
631
632 row[7] = (int)row[7] | localizedControl.Attributes;
633
634 if (!String.IsNullOrEmpty(localizedControl.Text))
635 {
636 row[9] = localizedControl.Text;
637 }
638 }
639 }
640 }
641 }
642
643 /// <summary>
644 /// Copy file data between transform substorages and the patch output object
645 /// </summary>
646 /// <param name="output">The output to bind.</param>
647 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
648 private IEnumerable<FileFacade> CopyFromTransformData(Output output)
649 {
650 var command = new CopyTransformDataCommand();
651 command.CopyOutFileRows = true;
652 command.Output = output;
653 command.TableDefinitions = this.TableDefinitions;
654 command.Execute();
655
656 return command.FileFacades;
657 }
658
659 /// <summary>
660 /// Copy file data between transform substorages and the patch output object
661 /// </summary>
662 /// <param name="output">The output to bind.</param>
663 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
664 private void CopyToTransformData(Output output)
665 {
666 var command = new CopyTransformDataCommand();
667 command.CopyOutFileRows = false;
668 command.Output = output;
669 command.TableDefinitions = this.TableDefinitions;
670 command.Execute();
671 }
672
673 private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows)
674 {
675 // Calculate sequence numbers and media disk id layout for all file media information objects.
676 if (OutputType.Module == outputType)
677 {
678 int lastSequence = 0;
679 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
680 {
681 facade.File.Sequence = ++lastSequence;
682 }
683 }
684 else
685 {
686 int lastSequence = 0;
687 MediaRow mediaRow = null;
688 Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>();
689
690 // sequence the non-patch-added files
691 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
692 {
693 if (null == mediaRow)
694 {
695 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
696 if (OutputType.Patch == outputType)
697 {
698 // patch Media cannot start at zero
699 lastSequence = mediaRow.LastSequence;
700 }
701 }
702 else if (mediaRow.DiskId != facade.WixFile.DiskId)
703 {
704 mediaRow.LastSequence = lastSequence;
705 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
706 }
707
708 if (0 < facade.WixFile.PatchGroup)
709 {
710 List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup];
711
712 if (null == patchGroup)
713 {
714 patchGroup = new List<FileFacade>();
715 patchGroups.Add(facade.WixFile.PatchGroup, patchGroup);
716 }
717
718 patchGroup.Add(facade);
719 }
720 else
721 {
722 facade.File.Sequence = ++lastSequence;
723 }
724 }
725
726 if (null != mediaRow)
727 {
728 mediaRow.LastSequence = lastSequence;
729 mediaRow = null;
730 }
731
732 // sequence the patch-added files
733 foreach (List<FileFacade> patchGroup in patchGroups.Values)
734 {
735 foreach (FileFacade facade in patchGroup)
736 {
737 if (null == mediaRow)
738 {
739 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
740 }
741 else if (mediaRow.DiskId != facade.WixFile.DiskId)
742 {
743 mediaRow.LastSequence = lastSequence;
744 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
745 }
746
747 facade.File.Sequence = ++lastSequence;
748 }
749 }
750
751 if (null != mediaRow)
752 {
753 mediaRow.LastSequence = lastSequence;
754 }
755 }
756 }
757
758 /// <summary>
759 /// Set the guids for components with generatable guids.
760 /// </summary>
761 /// <param name="output">Internal representation of the database to operate on.</param>
762 private void SetComponentGuids(Output output)
763 {
764 Table componentTable = output.Tables["Component"];
765 if (null != componentTable)
766 {
767 Hashtable registryKeyRows = null;
768 Hashtable directories = null;
769 Hashtable componentIdGenSeeds = null;
770 Dictionary<string, List<FileRow>> fileRows = null;
771
772 // find components with generatable guids
773 foreach (ComponentRow componentRow in componentTable.Rows)
774 {
775 // component guid will be generated
776 if ("*" == componentRow.Guid)
777 {
778 if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath)
779 {
780 Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers));
781 }
782 else if (componentRow.IsRegistryKeyPath)
783 {
784 if (null == registryKeyRows)
785 {
786 Table registryTable = output.Tables["Registry"];
787
788 registryKeyRows = new Hashtable(registryTable.Rows.Count);
789
790 foreach (Row registryRow in registryTable.Rows)
791 {
792 registryKeyRows.Add((string)registryRow[0], registryRow);
793 }
794 }
795
796 Row foundRow = registryKeyRows[componentRow.KeyPath] as Row;
797
798 string bitness = componentRow.Is64Bit ? "64" : String.Empty;
799 if (null != foundRow)
800 {
801 string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]);
802 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant();
803 }
804 }
805 else // must be a File KeyPath
806 {
807 // if the directory table hasn't been loaded into an indexed hash
808 // of directory ids to target names do that now.
809 if (null == directories)
810 {
811 Table directoryTable = output.Tables["Directory"];
812
813 int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0;
814
815 directories = new Hashtable(numDirectoryTableRows);
816
817 // get the target paths for all directories
818 if (null != directoryTable)
819 {
820 foreach (Row row in directoryTable.Rows)
821 {
822 // if the directory Id already exists, we will skip it here since
823 // checking for duplicate primary keys is done later when importing tables
824 // into database
825 if (directories.ContainsKey(row[0]))
826 {
827 continue;
828 }
829
830 string targetName = Common.GetName((string)row[2], false, true);
831 directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName));
832 }
833 }
834 }
835
836 // if the component id generation seeds have not been indexed
837 // from the WixDirectory table do that now.
838 if (null == componentIdGenSeeds)
839 {
840 Table wixDirectoryTable = output.Tables["WixDirectory"];
841
842 int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0;
843
844 componentIdGenSeeds = new Hashtable(numWixDirectoryRows);
845
846 // if there are any WixDirectory rows, build up the Component Guid
847 // generation seeds indexed by Directory/@Id.
848 if (null != wixDirectoryTable)
849 {
850 foreach (Row row in wixDirectoryTable.Rows)
851 {
852 componentIdGenSeeds.Add(row[0], (string)row[1]);
853 }
854 }
855 }
856
857 // if the file rows have not been indexed by File.Component yet
858 // then do that now
859 if (null == fileRows)
860 {
861 Table fileTable = output.Tables["File"];
862
863 int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0;
864
865 fileRows = new Dictionary<string, List<FileRow>>(numFileRows);
866
867 if (null != fileTable)
868 {
869 foreach (FileRow file in fileTable.Rows)
870 {
871 List<FileRow> files;
872 if (!fileRows.TryGetValue(file.Component, out files))
873 {
874 files = new List<FileRow>();
875 fileRows.Add(file.Component, files);
876 }
877
878 files.Add(file);
879 }
880 }
881 }
882
883 // validate component meets all the conditions to have a generated guid
884 List<FileRow> currentComponentFiles = fileRows[componentRow.Component];
885 int numFilesInComponent = currentComponentFiles.Count;
886 string path = null;
887
888 foreach (FileRow fileRow in currentComponentFiles)
889 {
890 if (fileRow.File == componentRow.KeyPath)
891 {
892 // calculate the key file's canonical target path
893 string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true);
894 string fileName = Common.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture);
895 path = Path.Combine(directoryPath, fileName);
896
897 // find paths that are not canonicalized
898 if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
899 path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
900 path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
901 path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
902 path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
903 path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
904 {
905 Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path));
906 }
907
908 // if component has more than one file, the key path must be versioned
909 if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version))
910 {
911 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers));
912 }
913 }
914 else
915 {
916 // not a key path, so it must be an unversioned file if component has more than one file
917 if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version))
918 {
919 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers));
920 }
921 }
922 }
923
924 // if the rules were followed, reward with a generated guid
925 if (!Messaging.Instance.EncounteredError)
926 {
927 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant();
928 }
929 }
930 }
931 }
932 }
933 }
934
935 /// <summary>
936 /// Creates instance transform substorages in the output.
937 /// </summary>
938 /// <param name="output">Output containing instance transform definitions.</param>
939 private void CreateInstanceTransforms(Output output)
940 {
941 // Create and add substorages for instance transforms.
942 Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"];
943 if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count)
944 {
945 string targetProductCode = null;
946 string targetUpgradeCode = null;
947 string targetProductVersion = null;
948
949 Table targetSummaryInformationTable = output.Tables["_SummaryInformation"];
950 Table targetPropertyTable = output.Tables["Property"];
951
952 // Get the data from target database
953 foreach (Row propertyRow in targetPropertyTable.Rows)
954 {
955 if ("ProductCode" == (string)propertyRow[0])
956 {
957 targetProductCode = (string)propertyRow[1];
958 }
959 else if ("ProductVersion" == (string)propertyRow[0])
960 {
961 targetProductVersion = (string)propertyRow[1];
962 }
963 else if ("UpgradeCode" == (string)propertyRow[0])
964 {
965 targetUpgradeCode = (string)propertyRow[1];
966 }
967 }
968
969 // Index the Instance Component Rows.
970 Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>();
971 Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"];
972 if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count)
973 {
974 foreach (Row row in targetInstanceComponentTable.Rows)
975 {
976 // Build up all the instances, we'll get the Components rows from the real Component table.
977 instanceComponentGuids.Add((string)row[0], null);
978 }
979
980 Table targetComponentTable = output.Tables["Component"];
981 foreach (ComponentRow componentRow in targetComponentTable.Rows)
982 {
983 string component = (string)componentRow[0];
984 if (instanceComponentGuids.ContainsKey(component))
985 {
986 instanceComponentGuids[component] = componentRow;
987 }
988 }
989 }
990
991 // Generate the instance transforms
992 foreach (Row instanceRow in wixInstanceTransformsTable.Rows)
993 {
994 string instanceId = (string)instanceRow[0];
995
996 Output instanceTransform = new Output(instanceRow.SourceLineNumbers);
997 instanceTransform.Type = OutputType.Transform;
998 instanceTransform.Codepage = output.Codepage;
999
1000 Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
1001 string targetPlatformAndLanguage = null;
1002
1003 foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows)
1004 {
1005 if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE
1006 {
1007 targetPlatformAndLanguage = (string)summaryInformationRow[1];
1008 }
1009
1010 // Copy the row's data to the transform.
1011 Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null);
1012 copyOfSummaryRow[0] = summaryInformationRow[0];
1013 copyOfSummaryRow[1] = summaryInformationRow[1];
1014 }
1015
1016 // Modify the appropriate properties.
1017 Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]);
1018
1019 // Change the ProductCode property
1020 string productCode = (string)instanceRow[2];
1021 if ("*" == productCode)
1022 {
1023 productCode = Common.GenerateGuid();
1024 }
1025
1026 Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1027 productCodeRow.Operation = RowOperation.Modify;
1028 productCodeRow.Fields[1].Modified = true;
1029 productCodeRow[0] = "ProductCode";
1030 productCodeRow[1] = productCode;
1031
1032 // Change the instance property
1033 Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1034 instanceIdRow.Operation = RowOperation.Modify;
1035 instanceIdRow.Fields[1].Modified = true;
1036 instanceIdRow[0] = (string)instanceRow[1];
1037 instanceIdRow[1] = instanceId;
1038
1039 if (null != instanceRow[3])
1040 {
1041 // Change the ProductName property
1042 Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1043 productNameRow.Operation = RowOperation.Modify;
1044 productNameRow.Fields[1].Modified = true;
1045 productNameRow[0] = "ProductName";
1046 productNameRow[1] = (string)instanceRow[3];
1047 }
1048
1049 if (null != instanceRow[4])
1050 {
1051 // Change the UpgradeCode property
1052 Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1053 upgradeCodeRow.Operation = RowOperation.Modify;
1054 upgradeCodeRow.Fields[1].Modified = true;
1055 upgradeCodeRow[0] = "UpgradeCode";
1056 upgradeCodeRow[1] = instanceRow[4];
1057
1058 // Change the Upgrade table
1059 Table targetUpgradeTable = output.Tables["Upgrade"];
1060 if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count)
1061 {
1062 string upgradeId = (string)instanceRow[4];
1063 Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]);
1064 foreach (Row row in targetUpgradeTable.Rows)
1065 {
1066 // In case they are upgrading other codes to this new product, leave the ones that don't match the
1067 // Product.UpgradeCode intact.
1068 if (targetUpgradeCode == (string)row[0])
1069 {
1070 Row upgradeRow = upgradeTable.CreateRow(null);
1071 upgradeRow.Operation = RowOperation.Add;
1072 upgradeRow.Fields[0].Modified = true;
1073 // I was hoping to be able to RowOperation.Modify, but that didn't appear to function.
1074 // upgradeRow.Fields[0].PreviousData = (string)row[0];
1075
1076 // Inserting a new Upgrade record with the updated UpgradeCode
1077 upgradeRow[0] = upgradeId;
1078 upgradeRow[1] = row[1];
1079 upgradeRow[2] = row[2];
1080 upgradeRow[3] = row[3];
1081 upgradeRow[4] = row[4];
1082 upgradeRow[5] = row[5];
1083 upgradeRow[6] = row[6];
1084
1085 // Delete the old row
1086 Row upgradeRemoveRow = upgradeTable.CreateRow(null);
1087 upgradeRemoveRow.Operation = RowOperation.Delete;
1088 upgradeRemoveRow[0] = row[0];
1089 upgradeRemoveRow[1] = row[1];
1090 upgradeRemoveRow[2] = row[2];
1091 upgradeRemoveRow[3] = row[3];
1092 upgradeRemoveRow[4] = row[4];
1093 upgradeRemoveRow[5] = row[5];
1094 upgradeRemoveRow[6] = row[6];
1095 }
1096 }
1097 }
1098 }
1099
1100 // If there are instance Components generate new GUIDs for them.
1101 if (0 < instanceComponentGuids.Count)
1102 {
1103 Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]);
1104 foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values)
1105 {
1106 string guid = targetComponentRow.Guid;
1107 if (!String.IsNullOrEmpty(guid))
1108 {
1109 Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers);
1110 instanceComponentRow.Operation = RowOperation.Modify;
1111 instanceComponentRow.Fields[1].Modified = true;
1112 instanceComponentRow[0] = targetComponentRow[0];
1113 instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture);
1114 instanceComponentRow[2] = targetComponentRow[2];
1115 instanceComponentRow[3] = targetComponentRow[3];
1116 instanceComponentRow[4] = targetComponentRow[4];
1117 instanceComponentRow[5] = targetComponentRow[5];
1118 }
1119 }
1120 }
1121
1122 // Update the summary information
1123 Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count);
1124 foreach (Row row in instanceSummaryInformationTable.Rows)
1125 {
1126 summaryRows[row[0]] = row;
1127
1128 if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
1129 {
1130 row[1] = targetPlatformAndLanguage;
1131 }
1132 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
1133 {
1134 row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode);
1135 }
1136 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
1137 {
1138 row[1] = 0;
1139 }
1140 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
1141 {
1142 row[1] = "4";
1143 }
1144 }
1145
1146 if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
1147 {
1148 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1149 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
1150 summaryRow[1] = targetPlatformAndLanguage;
1151 }
1152 else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
1153 {
1154 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1155 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
1156 summaryRow[1] = "0";
1157 }
1158 else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security))
1159 {
1160 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1161 summaryRow[0] = (int)SummaryInformation.Transform.Security;
1162 summaryRow[1] = "4";
1163 }
1164
1165 output.SubStorages.Add(new SubStorage(instanceId, instanceTransform));
1166 }
1167 }
1168 }
1169
1170 /// <summary>
1171 /// Validate that there are no duplicate GUIDs in the output.
1172 /// </summary>
1173 /// <remarks>
1174 /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a
1175 /// warning, as the conditions might be mutually exclusive.
1176 /// </remarks>
1177 private void ValidateComponentGuids(Output output)
1178 {
1179 Table componentTable = output.Tables["Component"];
1180 if (null != componentTable)
1181 {
1182 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
1183
1184 foreach (ComponentRow row in componentTable.Rows)
1185 {
1186 // we don't care about unmanaged components and if there's a * GUID remaining,
1187 // there's already an error that prevented it from being replaced with a real GUID.
1188 if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid)
1189 {
1190 bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
1191 bool allComponentsHaveConditions = thisComponentHasCondition;
1192
1193 if (componentGuidConditions.ContainsKey(row.Guid))
1194 {
1195 allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition;
1196
1197 if (allComponentsHaveConditions)
1198 {
1199 Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid));
1200 }
1201 else
1202 {
1203 Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid));
1204 }
1205 }
1206
1207 componentGuidConditions[row.Guid] = allComponentsHaveConditions;
1208 }
1209 }
1210 }
1211 }
1212
1213 /// <summary>
1214 /// Update Control and BBControl text by reading from files when necessary.
1215 /// </summary>
1216 /// <param name="output">Internal representation of the msi database to operate upon.</param>
1217 private void UpdateControlText(Output output)
1218 {
1219 UpdateControlTextCommand command = new UpdateControlTextCommand();
1220 command.BBControlTable = output.Tables["BBControl"];
1221 command.WixBBControlTable = output.Tables["WixBBControl"];
1222 command.ControlTable = output.Tables["Control"];
1223 command.WixControlTable = output.Tables["WixControl"];
1224 command.Execute();
1225 }
1226
1227 private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory)
1228 {
1229 string layout = null;
1230
1231 foreach (var extension in this.BackendExtensions)
1232 {
1233 layout = extension.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory);
1234 if (!String.IsNullOrEmpty(layout))
1235 {
1236 break;
1237 }
1238 }
1239
1240 // If no binder file manager resolved the layout, do the default behavior.
1241 if (String.IsNullOrEmpty(layout))
1242 {
1243 if (String.IsNullOrEmpty(mediaLayoutDirectory))
1244 {
1245 layout = layoutDirectory;
1246 }
1247 else if (Path.IsPathRooted(mediaLayoutDirectory))
1248 {
1249 layout = mediaLayoutDirectory;
1250 }
1251 else
1252 {
1253 layout = Path.Combine(layoutDirectory, mediaLayoutDirectory);
1254 }
1255 }
1256
1257 return layout;
1258 }
1259
1260 /// <summary>
1261 /// Creates the MSI/MSM/PCP database.
1262 /// </summary>
1263 /// <param name="output">Output to create database for.</param>
1264 /// <param name="databaseFile">The database file to create.</param>
1265 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
1266 /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
1267 private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
1268 {
1269 var command = new GenerateDatabaseCommand();
1270 command.Extensions = this.Extensions;
1271 command.Output = output;
1272 command.OutputPath = databaseFile;
1273 command.KeepAddedColumns = keepAddedColumns;
1274 command.UseSubDirectory = useSubdirectory;
1275 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
1276 command.TableDefinitions = this.TableDefinitions;
1277 command.TempFilesLocation = this.IntermediateFolder;
1278 command.Codepage = this.Codepage;
1279 command.Execute();
1280 }
1281 }
1282}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs
new file mode 100644
index 00000000..5471792d
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs
@@ -0,0 +1,135 @@
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.Databases
4{
5 using System;
6 using System.Globalization;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Binds the summary information table of a database.
11 /// </summary>
12 internal class BindSummaryInfoCommand
13 {
14 /// <summary>
15 /// The output to bind.
16 /// </summary>
17 public Output Output { private get; set; }
18
19 /// <summary>
20 /// Returns a flag indicating if files are compressed by default.
21 /// </summary>
22 public bool Compressed { get; private set; }
23
24 /// <summary>
25 /// Returns a flag indicating if uncompressed files use long filenames.
26 /// </summary>
27 public bool LongNames { get; private set; }
28
29 public int InstallerVersion { get; private set; }
30
31 /// <summary>
32 /// Modularization guid, or null if the output is not a module.
33 /// </summary>
34 public string ModularizationGuid { get; private set; }
35
36 public void Execute()
37 {
38 this.Compressed = false;
39 this.LongNames = false;
40 this.InstallerVersion = 0;
41 this.ModularizationGuid = null;
42
43 Table summaryInformationTable = this.Output.Tables["_SummaryInformation"];
44
45 if (null != summaryInformationTable)
46 {
47 bool foundCreateDataTime = false;
48 bool foundLastSaveDataTime = false;
49 bool foundCreatingApplication = false;
50 string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
51
52 foreach (Row summaryInformationRow in summaryInformationTable.Rows)
53 {
54 switch (summaryInformationRow.FieldAsInteger(0))
55 {
56 case 1: // PID_CODEPAGE
57 // make sure the code page is an int and not a web name or null
58 string codepage = summaryInformationRow.FieldAsString(1);
59
60 if (null == codepage)
61 {
62 codepage = "0";
63 }
64 else
65 {
66 summaryInformationRow[1] = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture);
67 }
68 break;
69 case 9: // PID_REVNUMBER
70 string packageCode = (string)summaryInformationRow[1];
71
72 if (OutputType.Module == this.Output.Type)
73 {
74 this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_');
75 }
76 else if ("*" == packageCode)
77 {
78 // set the revision number (package/patch code) if it should be automatically generated
79 summaryInformationRow[1] = Common.GenerateGuid();
80 }
81 break;
82 case 12: // PID_CREATE_DTM
83 foundCreateDataTime = true;
84 break;
85 case 13: // PID_LASTSAVE_DTM
86 foundLastSaveDataTime = true;
87 break;
88 case 14:
89 this.InstallerVersion = summaryInformationRow.FieldAsInteger(1);
90 break;
91 case 15: // PID_WORDCOUNT
92 if (OutputType.Patch == this.Output.Type)
93 {
94 this.LongNames = true;
95 this.Compressed = true;
96 }
97 else
98 {
99 this.LongNames = (0 == (summaryInformationRow.FieldAsInteger(1) & 1));
100 this.Compressed = (2 == (summaryInformationRow.FieldAsInteger(1) & 2));
101 }
102 break;
103 case 18: // PID_APPNAME
104 foundCreatingApplication = true;
105 break;
106 }
107 }
108
109 // add a summary information row for the create time/date property if its not already set
110 if (!foundCreateDataTime)
111 {
112 Row createTimeDateRow = summaryInformationTable.CreateRow(null);
113 createTimeDateRow[0] = 12;
114 createTimeDateRow[1] = now;
115 }
116
117 // add a summary information row for the last save time/date property if its not already set
118 if (!foundLastSaveDataTime)
119 {
120 Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null);
121 lastSaveTimeDateRow[0] = 13;
122 lastSaveTimeDateRow[1] = now;
123 }
124
125 // add a summary information row for the creating application property if its not already set
126 if (!foundCreatingApplication)
127 {
128 Row creatingApplicationRow = summaryInformationTable.CreateRow(null);
129 creatingApplicationRow[0] = 18;
130 creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString());
131 }
132 }
133 }
134 }
135}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
new file mode 100644
index 00000000..425d1f9c
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
@@ -0,0 +1,470 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Msi;
12 using WixToolset.Core.Native;
13
14 internal class BindTransformCommand
15 {
16 public IEnumerable<IBinderExtension> Extensions { private get; set; }
17
18 public TableDefinitionCollection TableDefinitions { private get; set; }
19
20 public string TempFilesLocation { private get; set; }
21
22 public Output Transform { private get; set; }
23
24 public string OutputPath { private get; set; }
25
26 public void Execute()
27 {
28 int transformFlags = 0;
29
30 Output targetOutput = new Output(null);
31 Output updatedOutput = new Output(null);
32
33 // TODO: handle added columns
34
35 // to generate a localized transform, both the target and updated
36 // databases need to have the same code page. the only reason to
37 // set different code pages is to support localized primary key
38 // columns, but that would only support deleting rows. if this
39 // becomes necessary, define a PreviousCodepage property on the
40 // Output class and persist this throughout transform generation.
41 targetOutput.Codepage = this.Transform.Codepage;
42 updatedOutput.Codepage = this.Transform.Codepage;
43
44 // remove certain Property rows which will be populated from summary information values
45 string targetUpgradeCode = null;
46 string updatedUpgradeCode = null;
47
48 Table propertyTable = this.Transform.Tables["Property"];
49 if (null != propertyTable)
50 {
51 for (int i = propertyTable.Rows.Count - 1; i >= 0; i--)
52 {
53 Row row = propertyTable.Rows[i];
54
55 if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0])
56 {
57 propertyTable.Rows.RemoveAt(i);
58
59 if ("UpgradeCode" == (string)row[0])
60 {
61 updatedUpgradeCode = (string)row[1];
62 }
63 }
64 }
65 }
66
67 Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
68 Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
69 Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]);
70 Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]);
71
72 // process special summary information values
73 foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows)
74 {
75 if ((int)SummaryInformation.Transform.CodePage == (int)row[0])
76 {
77 // convert from a web name if provided
78 string codePage = (string)row.Fields[1].Data;
79 if (null == codePage)
80 {
81 codePage = "0";
82 }
83 else
84 {
85 codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture);
86 }
87
88 string previousCodePage = (string)row.Fields[1].PreviousData;
89 if (null == previousCodePage)
90 {
91 previousCodePage = "0";
92 }
93 else
94 {
95 previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture);
96 }
97
98 Row targetCodePageRow = targetSummaryInfo.CreateRow(null);
99 targetCodePageRow[0] = 1; // PID_CODEPAGE
100 targetCodePageRow[1] = previousCodePage;
101
102 Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null);
103 updatedCodePageRow[0] = 1; // PID_CODEPAGE
104 updatedCodePageRow[1] = codePage;
105 }
106 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ||
107 (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
108 {
109 // the target language
110 string[] propertyData = ((string)row[1]).Split(';');
111 string lang = 2 == propertyData.Length ? propertyData[1] : "0";
112
113 Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo;
114 Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable;
115
116 Row productLanguageRow = tempPropertyTable.CreateRow(null);
117 productLanguageRow[0] = "ProductLanguage";
118 productLanguageRow[1] = lang;
119
120 // set the platform;language on the MSI to be generated
121 Row templateRow = tempSummaryInfo.CreateRow(null);
122 templateRow[0] = 7; // PID_TEMPLATE
123 templateRow[1] = (string)row[1];
124 }
125 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
126 {
127 string[] propertyData = ((string)row[1]).Split(';');
128
129 Row targetProductCodeRow = targetPropertyTable.CreateRow(null);
130 targetProductCodeRow[0] = "ProductCode";
131 targetProductCodeRow[1] = propertyData[0].Substring(0, 38);
132
133 Row targetProductVersionRow = targetPropertyTable.CreateRow(null);
134 targetProductVersionRow[0] = "ProductVersion";
135 targetProductVersionRow[1] = propertyData[0].Substring(38);
136
137 Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null);
138 updatedProductCodeRow[0] = "ProductCode";
139 updatedProductCodeRow[1] = propertyData[1].Substring(0, 38);
140
141 Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null);
142 updatedProductVersionRow[0] = "ProductVersion";
143 updatedProductVersionRow[1] = propertyData[1].Substring(38);
144
145 // UpgradeCode is optional and may not exists in the target
146 // or upgraded databases, so do not include a null-valued
147 // UpgradeCode property.
148
149 targetUpgradeCode = propertyData[2];
150 if (!String.IsNullOrEmpty(targetUpgradeCode))
151 {
152 Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null);
153 targetUpgradeCodeRow[0] = "UpgradeCode";
154 targetUpgradeCodeRow[1] = targetUpgradeCode;
155
156 // If the target UpgradeCode is specified, an updated
157 // UpgradeCode is required.
158 if (String.IsNullOrEmpty(updatedUpgradeCode))
159 {
160 updatedUpgradeCode = targetUpgradeCode;
161 }
162 }
163
164 if (!String.IsNullOrEmpty(updatedUpgradeCode))
165 {
166 Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null);
167 updatedUpgradeCodeRow[0] = "UpgradeCode";
168 updatedUpgradeCodeRow[1] = updatedUpgradeCode;
169 }
170 }
171 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
172 {
173 transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
174 }
175 else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0])
176 {
177 // PID_LASTPRINTED should be null for transforms
178 row.Operation = RowOperation.None;
179 }
180 else
181 {
182 // add everything else as is
183 Row targetRow = targetSummaryInfo.CreateRow(null);
184 targetRow[0] = row[0];
185 targetRow[1] = row[1];
186
187 Row updatedRow = updatedSummaryInfo.CreateRow(null);
188 updatedRow[0] = row[0];
189 updatedRow[1] = row[1];
190 }
191 }
192
193 // Validate that both databases have an UpgradeCode if the
194 // authoring transform will validate the UpgradeCode; otherwise,
195 // MsiCreateTransformSummaryinfo() will fail with 1620.
196 if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 &&
197 (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode)))
198 {
199 Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired());
200 }
201
202 string emptyFile = null;
203
204 foreach (Table table in this.Transform.Tables)
205 {
206 // Ignore unreal tables when building transforms except the _Stream table.
207 // These tables are ignored when generating the database so there is no reason
208 // to process them here.
209 if (table.Definition.Unreal && "_Streams" != table.Name)
210 {
211 continue;
212 }
213
214 // process table operations
215 switch (table.Operation)
216 {
217 case TableOperation.Add:
218 updatedOutput.EnsureTable(table.Definition);
219 break;
220 case TableOperation.Drop:
221 targetOutput.EnsureTable(table.Definition);
222 continue;
223 default:
224 targetOutput.EnsureTable(table.Definition);
225 updatedOutput.EnsureTable(table.Definition);
226 break;
227 }
228
229 // process row operations
230 foreach (Row row in table.Rows)
231 {
232 switch (row.Operation)
233 {
234 case RowOperation.Add:
235 Table updatedTable = updatedOutput.EnsureTable(table.Definition);
236 updatedTable.Rows.Add(row);
237 continue;
238 case RowOperation.Delete:
239 Table targetTable = targetOutput.EnsureTable(table.Definition);
240 targetTable.Rows.Add(row);
241
242 // fill-in non-primary key values
243 foreach (Field field in row.Fields)
244 {
245 if (!field.Column.PrimaryKey)
246 {
247 if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable)
248 {
249 field.Data = field.Column.MinValue;
250 }
251 else if (ColumnType.Object == field.Column.Type)
252 {
253 if (null == emptyFile)
254 {
255 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
256 }
257
258 field.Data = emptyFile;
259 }
260 else
261 {
262 field.Data = "0";
263 }
264 }
265 }
266 continue;
267 }
268
269 // Assure that the file table's sequence is populated
270 if ("File" == table.Name)
271 {
272 foreach (Row fileRow in table.Rows)
273 {
274 if (null == fileRow[7])
275 {
276 if (RowOperation.Add == fileRow.Operation)
277 {
278 Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0]));
279 break;
280 }
281
282 // Set to 1 to prevent invalid IDT file from being generated
283 fileRow[7] = 1;
284 }
285 }
286 }
287
288 // process modified and unmodified rows
289 bool modifiedRow = false;
290 Row targetRow = new Row(null, table.Definition);
291 Row updatedRow = row;
292 for (int i = 0; i < row.Fields.Length; i++)
293 {
294 Field updatedField = row.Fields[i];
295
296 if (updatedField.Modified)
297 {
298 // set a different value in the target row to ensure this value will be modified during transform generation
299 if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable)
300 {
301 if (null == updatedField.Data || 1 != (int)updatedField.Data)
302 {
303 targetRow[i] = 1;
304 }
305 else
306 {
307 targetRow[i] = 2;
308 }
309 }
310 else if (ColumnType.Object == updatedField.Column.Type)
311 {
312 if (null == emptyFile)
313 {
314 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
315 }
316
317 targetRow[i] = emptyFile;
318 }
319 else
320 {
321 if ("0" != (string)updatedField.Data)
322 {
323 targetRow[i] = "0";
324 }
325 else
326 {
327 targetRow[i] = "1";
328 }
329 }
330
331 modifiedRow = true;
332 }
333 else if (ColumnType.Object == updatedField.Column.Type)
334 {
335 ObjectField objectField = (ObjectField)updatedField;
336
337 // create an empty file for comparing against
338 if (null == objectField.PreviousData)
339 {
340 if (null == emptyFile)
341 {
342 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
343 }
344
345 targetRow[i] = emptyFile;
346 modifiedRow = true;
347 }
348 else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data))
349 {
350 targetRow[i] = objectField.PreviousData;
351 modifiedRow = true;
352 }
353 }
354 else // unmodified
355 {
356 if (null != updatedField.Data)
357 {
358 targetRow[i] = updatedField.Data;
359 }
360 }
361 }
362
363 // modified rows and certain special rows go in the target and updated msi databases
364 if (modifiedRow ||
365 ("Property" == table.Name &&
366 ("ProductCode" == (string)row[0] ||
367 "ProductLanguage" == (string)row[0] ||
368 "ProductVersion" == (string)row[0] ||
369 "UpgradeCode" == (string)row[0])))
370 {
371 Table targetTable = targetOutput.EnsureTable(table.Definition);
372 targetTable.Rows.Add(targetRow);
373
374 Table updatedTable = updatedOutput.EnsureTable(table.Definition);
375 updatedTable.Rows.Add(updatedRow);
376 }
377 }
378 }
379
380 //foreach (BinderExtension extension in this.Extensions)
381 //{
382 // extension.PostBind(this.Context);
383 //}
384
385 // Any errors encountered up to this point can cause errors during generation.
386 if (Messaging.Instance.EncounteredError)
387 {
388 return;
389 }
390
391 string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath);
392 string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi"));
393 string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi"));
394
395 try
396 {
397 if (!String.IsNullOrEmpty(emptyFile))
398 {
399 using (FileStream fileStream = File.Create(emptyFile))
400 {
401 }
402 }
403
404 this.GenerateDatabase(targetOutput, targetDatabaseFile, false);
405 this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true);
406
407 // make sure the directory exists
408 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
409
410 // create the transform file
411 using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly))
412 {
413 using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly))
414 {
415 if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath))
416 {
417 updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF));
418 }
419 else
420 {
421 Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers));
422 }
423 }
424 }
425 }
426 finally
427 {
428 if (!String.IsNullOrEmpty(emptyFile))
429 {
430 File.Delete(emptyFile);
431 }
432 }
433 }
434
435 private bool CompareFiles(string targetFile, string updatedFile)
436 {
437 bool? compared = null;
438 foreach (var extension in this.Extensions)
439 {
440 compared = extension.CompareFiles(targetFile, updatedFile);
441 if (compared.HasValue)
442 {
443 break;
444 }
445 }
446
447 if (!compared.HasValue)
448 {
449 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
450 }
451
452 return compared.Value;
453 }
454
455 private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns)
456 {
457 var command = new GenerateDatabaseCommand();
458 command.Codepage = output.Codepage;
459 command.Extensions = this.Extensions;
460 command.KeepAddedColumns = keepAddedColumns;
461 command.Output = output;
462 command.OutputPath = outputPath;
463 command.TableDefinitions = this.TableDefinitions;
464 command.TempFilesLocation = this.TempFilesLocation;
465 command.SuppressAddingValidationRows = true;
466 command.UseSubDirectory = true;
467 command.Execute();
468 }
469 }
470}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
new file mode 100644
index 00000000..b2cc76fc
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
@@ -0,0 +1,177 @@
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.Databases
4{
5 using System;
6 using System.Collections;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using WixToolset.Core.Bind;
11 using WixToolset.Core.Cab;
12 using WixToolset.Data;
13
14 /// <summary>
15 /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple
16 /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished.
17 /// </summary>
18 internal sealed class CabinetBuilder
19 {
20 private Queue cabinetWorkItems;
21 private object lockObject;
22 private int threadCount;
23
24 // Address of Binder's callback function for Cabinet Splitting
25 private IntPtr newCabNamesCallBackAddress;
26
27 public int MaximumCabinetSizeForLargeFileSplitting { get; set; }
28
29 public int MaximumUncompressedMediaSize { get; set; }
30
31 /// <summary>
32 /// Instantiate a new CabinetBuilder.
33 /// </summary>
34 /// <param name="threadCount">number of threads to use</param>
35 /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
36 public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress)
37 {
38 if (0 >= threadCount)
39 {
40 throw new ArgumentOutOfRangeException("threadCount");
41 }
42
43 this.cabinetWorkItems = new Queue();
44 this.lockObject = new object();
45
46 this.threadCount = threadCount;
47
48 // Set Address of Binder's callback function for Cabinet Splitting
49 this.newCabNamesCallBackAddress = newCabNamesCallBackAddress;
50 }
51
52 /// <summary>
53 /// Enqueues a CabinetWorkItem to the queue.
54 /// </summary>
55 /// <param name="cabinetWorkItem">cabinet work item</param>
56 public void Enqueue(CabinetWorkItem cabinetWorkItem)
57 {
58 this.cabinetWorkItems.Enqueue(cabinetWorkItem);
59 }
60
61 /// <summary>
62 /// Create the queued cabinets.
63 /// </summary>
64 /// <returns>error message number (zero if no error)</returns>
65 public void CreateQueuedCabinets()
66 {
67 // don't create more threads than the number of cabinets to build
68 if (this.cabinetWorkItems.Count < this.threadCount)
69 {
70 this.threadCount = this.cabinetWorkItems.Count;
71 }
72
73 if (0 < this.threadCount)
74 {
75 Thread[] threads = new Thread[this.threadCount];
76
77 for (int i = 0; i < threads.Length; i++)
78 {
79 threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems));
80 threads[i].Start();
81 }
82
83 // wait for all threads to finish
84 foreach (Thread thread in threads)
85 {
86 thread.Join();
87 }
88 }
89 }
90
91 /// <summary>
92 /// This function gets called by multiple threads to do actual work.
93 /// It takes one work item at a time and calls this.CreateCabinet().
94 /// It does not return until cabinetWorkItems queue is empty
95 /// </summary>
96 private void ProcessWorkItems()
97 {
98 try
99 {
100 while (true)
101 {
102 CabinetWorkItem cabinetWorkItem;
103
104 lock (this.cabinetWorkItems)
105 {
106 // check if there are any more cabinets to create
107 if (0 == this.cabinetWorkItems.Count)
108 {
109 break;
110 }
111
112 cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue();
113 }
114
115 // create a cabinet
116 this.CreateCabinet(cabinetWorkItem);
117 }
118 }
119 catch (WixException we)
120 {
121 Messaging.Instance.OnMessage(we.Error);
122 }
123 catch (Exception e)
124 {
125 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace));
126 }
127 }
128
129 /// <summary>
130 /// Creates a cabinet using the wixcab.dll interop layer.
131 /// </summary>
132 /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param>
133 private void CreateCabinet(CabinetWorkItem cabinetWorkItem)
134 {
135 Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile));
136
137 int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting
138 ulong maxPreCompressedSizeInBytes = 0;
139
140 if (MaximumCabinetSizeForLargeFileSplitting != 0)
141 {
142 // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize
143 // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file
144 if (1 == cabinetWorkItem.FileFacades.Count())
145 {
146 // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs
147 // Get the Value for Max Uncompressed Media Size
148 maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024;
149
150 foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row
151 {
152 if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes)
153 {
154 // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting
155 maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting;
156 }
157 }
158 }
159 }
160
161 // create the cabinet file
162 string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile);
163 string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile);
164
165 using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel))
166 {
167 foreach (FileFacade facade in cabinetWorkItem.FileFacades)
168 {
169 cab.AddFile(facade);
170 }
171
172 cab.Complete(newCabNamesCallBackAddress);
173 }
174 }
175 }
176}
177
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs
new file mode 100644
index 00000000..df1ccecf
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs
@@ -0,0 +1,122 @@
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.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.Cab;
10 using WixToolset.Core.Bind;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13
14 public class CabinetResolver
15 {
16 public CabinetResolver(string cabCachePath, IEnumerable<IWindowsInstallerBackendExtension> backendExtensions)
17 {
18 this.CabCachePath = cabCachePath;
19
20 this.BackendExtensions = backendExtensions;
21 }
22
23 private string CabCachePath { get; }
24
25 private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; }
26
27 public ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
28 {
29 var filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
30
31 ResolvedCabinet resolved = null;
32
33 foreach (var extension in this.BackendExtensions)
34 {
35 resolved = extension.ResolveCabinet(cabinetPath, filesWithPath);
36
37 if (null != resolved)
38 {
39 return resolved;
40 }
41 }
42
43 // By default cabinet should be built and moved to the suggested location.
44 resolved = new ResolvedCabinet() { BuildOption = CabinetBuildOption.BuildAndMove, Path = cabinetPath };
45
46 // If a cabinet cache path was provided, change the location for the cabinet
47 // to be built to and check if there is a cabinet that can be reused.
48 if (!String.IsNullOrEmpty(this.CabCachePath))
49 {
50 string cabinetName = Path.GetFileName(cabinetPath);
51 resolved.Path = Path.Combine(this.CabCachePath, cabinetName);
52
53 if (CheckFileExists(resolved.Path))
54 {
55 // Assume that none of the following are true:
56 // 1. any files are added or removed
57 // 2. order of files changed or names changed
58 // 3. modified time changed
59 bool cabinetValid = true;
60
61 // Need to force garbage collection of WixEnumerateCab to ensure the handle
62 // associated with it is closed before it is reused.
63 using (var wixEnumerateCab = new WixEnumerateCab())
64 {
65 List<CabinetFileInfo> fileList = wixEnumerateCab.Enumerate(resolved.Path);
66
67 if (filesWithPath.Count() != fileList.Count)
68 {
69 cabinetValid = false;
70 }
71 else
72 {
73 int i = 0;
74 foreach (BindFileWithPath file in filesWithPath)
75 {
76 // First check that the file identifiers match because that is quick and easy.
77 CabinetFileInfo cabFileInfo = fileList[i];
78 cabinetValid = (cabFileInfo.FileId == file.Id);
79 if (cabinetValid)
80 {
81 // Still valid so ensure the file sizes are the same.
82 FileInfo fileInfo = new FileInfo(file.Path);
83 cabinetValid = (cabFileInfo.Size == fileInfo.Length);
84 if (cabinetValid)
85 {
86 // Still valid so ensure the source time stamp hasn't changed. Thus we need
87 // to convert the source file time stamp into a cabinet compatible data/time.
88 Native.CabInterop.DateTimeToCabDateAndTime(fileInfo.LastWriteTime, out var sourceCabDate, out var sourceCabTime);
89 cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime);
90 }
91 }
92
93 if (!cabinetValid)
94 {
95 break;
96 }
97
98 i++;
99 }
100 }
101 }
102
103 resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy;
104 }
105 }
106
107 return resolved;
108 }
109
110 private static bool CheckFileExists(string path)
111 {
112 try
113 {
114 return File.Exists(path);
115 }
116 catch (ArgumentException)
117 {
118 throw new WixException(WixErrors.IllegalCharactersInPath(path));
119 }
120 }
121 }
122}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
new file mode 100644
index 00000000..dcafcd36
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
@@ -0,0 +1,79 @@
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.Databases
4{
5 using System.Collections.Generic;
6 using WixToolset.Core.Bind;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 /// <summary>
11 /// A cabinet builder work item.
12 /// </summary>
13 internal sealed class CabinetWorkItem
14 {
15 private string cabinetFile;
16 private CompressionLevel compressionLevel;
17 //private BinderFileManager binderFileManager;
18 private int maxThreshold;
19
20 /// <summary>
21 /// Instantiate a new CabinetWorkItem.
22 /// </summary>
23 /// <param name="fileFacades">The collection of files in this cabinet.</param>
24 /// <param name="cabinetFile">The cabinet file.</param>
25 /// <param name="maxThreshold">Maximum threshold for each cabinet.</param>
26 /// <param name="compressionLevel">The compression level of the cabinet.</param>
27 /// <param name="binderFileManager">The binder file manager.</param>
28 public CabinetWorkItem(IEnumerable<FileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/)
29 {
30 this.cabinetFile = cabinetFile;
31 this.compressionLevel = compressionLevel;
32 this.FileFacades = fileFacades;
33 //this.binderFileManager = binderFileManager;
34 this.maxThreshold = maxThreshold;
35 }
36
37 /// <summary>
38 /// Gets the cabinet file.
39 /// </summary>
40 /// <value>The cabinet file.</value>
41 public string CabinetFile
42 {
43 get { return this.cabinetFile; }
44 }
45
46 /// <summary>
47 /// Gets the compression level of the cabinet.
48 /// </summary>
49 /// <value>The compression level of the cabinet.</value>
50 public CompressionLevel CompressionLevel
51 {
52 get { return this.compressionLevel; }
53 }
54
55 /// <summary>
56 /// Gets the collection of files in this cabinet.
57 /// </summary>
58 /// <value>The collection of files in this cabinet.</value>
59 public IEnumerable<FileFacade> FileFacades { get; private set; }
60
61 /// <summary>
62 /// Gets the binder file manager.
63 /// </summary>
64 /// <value>The binder file manager.</value>
65 //public BinderFileManager BinderFileManager
66 //{
67 // get { return this.binderFileManager; }
68 //}
69
70 /// <summary>
71 /// Gets the max threshold.
72 /// </summary>
73 /// <value>The maximum threshold for a folder in a cabinet.</value>
74 public int MaxThreshold
75 {
76 get { return this.maxThreshold; }
77 }
78 }
79}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
new file mode 100644
index 00000000..d4d3799f
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
@@ -0,0 +1,91 @@
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.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using WixToolset.MergeMod;
9
10 /// <summary>
11 /// Callback object for configurable merge modules.
12 /// </summary>
13 internal sealed class ConfigurationCallback : IMsmConfigureModule
14 {
15 private const int SOk = 0x0;
16 private const int SFalse = 0x1;
17 private Hashtable configurationData;
18
19 /// <summary>
20 /// Creates a ConfigurationCallback object.
21 /// </summary>
22 /// <param name="configData">String to break up into name/value pairs.</param>
23 public ConfigurationCallback(string configData)
24 {
25 if (String.IsNullOrEmpty(configData))
26 {
27 throw new ArgumentNullException("configData");
28 }
29
30 string[] pairs = configData.Split(',');
31 this.configurationData = new Hashtable(pairs.Length);
32 for (int i = 0; i < pairs.Length; ++i)
33 {
34 string[] nameVal = pairs[i].Split('=');
35 string name = nameVal[0];
36 string value = nameVal[1];
37
38 name = name.Replace("%2C", ",");
39 name = name.Replace("%3D", "=");
40 name = name.Replace("%25", "%");
41
42 value = value.Replace("%2C", ",");
43 value = value.Replace("%3D", "=");
44 value = value.Replace("%25", "%");
45
46 this.configurationData[name] = value;
47 }
48 }
49
50 /// <summary>
51 /// Returns text data based on name.
52 /// </summary>
53 /// <param name="name">Name of value to return.</param>
54 /// <param name="configData">Out param to put configuration data into.</param>
55 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
56 public int ProvideTextData(string name, out string configData)
57 {
58 if (this.configurationData.Contains(name))
59 {
60 configData = (string)this.configurationData[name];
61 return SOk;
62 }
63 else
64 {
65 configData = null;
66 return SFalse;
67 }
68 }
69
70 /// <summary>
71 /// Returns integer data based on name.
72 /// </summary>
73 /// <param name="name">Name of value to return.</param>
74 /// <param name="configData">Out param to put configuration data into.</param>
75 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
76 public int ProvideIntegerData(string name, out int configData)
77 {
78 if (this.configurationData.Contains(name))
79 {
80 string val = (string)this.configurationData[name];
81 configData = Convert.ToInt32(val, CultureInfo.InvariantCulture);
82 return SOk;
83 }
84 else
85 {
86 configData = 0;
87 return SFalse;
88 }
89 }
90 }
91}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
new file mode 100644
index 00000000..6388a352
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
@@ -0,0 +1,606 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Extensibility;
11 using WixToolset.Core.Native;
12 using WixToolset.Core.Bind;
13
14 internal class CopyTransformDataCommand
15 {
16 public bool CopyOutFileRows { private get; set; }
17
18 public IEnumerable<IBinderExtension> Extensions { private get; set; }
19
20 public Output Output { private get; set; }
21
22 public TableDefinitionCollection TableDefinitions { private get; set; }
23
24 public IEnumerable<FileFacade> FileFacades { get; private set; }
25
26 public void Execute()
27 {
28 Debug.Assert(OutputType.Patch != this.Output.Type);
29
30 List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null;
31
32#if false // TODO: Fix this patching related code to work correctly with FileFacades.
33 bool copyToPatch = (allFileRows != null);
34 bool copyFromPatch = !copyToPatch;
35
36 RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>();
37
38 Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
39
40 Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
41 Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
42
43 if (copyFromPatch)
44 {
45 // index patch files by diskId+fileId
46 foreach (WixFileRow patchFileRow in patchFileTable.Rows)
47 {
48 int diskId = patchFileRow.DiskId;
49 RowDictionary<WixFileRow> mediaFileRows;
50 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
51 {
52 mediaFileRows = new RowDictionary<WixFileRow>();
53 patchMediaFileRows.Add(diskId, mediaFileRows);
54 }
55
56 mediaFileRows.Add(patchFileRow);
57 }
58
59 Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
60 patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable);
61 }
62
63 // index paired transforms
64 Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>();
65 foreach (SubStorage substorage in this.Output.SubStorages)
66 {
67 if (substorage.Name.StartsWith("#"))
68 {
69 pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data);
70 }
71 }
72
73 try
74 {
75 // copy File bind data into substorages
76 foreach (SubStorage substorage in this.Output.SubStorages)
77 {
78 if (substorage.Name.StartsWith("#"))
79 {
80 // no changes necessary for paired transforms
81 continue;
82 }
83
84 Output mainTransform = substorage.Data;
85 Table mainWixFileTable = mainTransform.Tables["WixFile"];
86 Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"];
87
88 this.FileManagerCore.ActiveSubStorage = substorage;
89
90 RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable);
91 RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>();
92
93 Table mainFileTable = mainTransform.Tables["File"];
94 Output pairedTransform = (Output)pairedTransforms[substorage.Name];
95
96 // copy Media.LastSequence and index the MsiFileHash table if it exists.
97 if (copyFromPatch)
98 {
99 Table pairedMediaTable = pairedTransform.Tables["Media"];
100 foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
101 {
102 MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
103 pairedMediaRow.Fields[1] = patchMediaRow.Fields[1];
104 }
105
106 if (null != mainMsiFileHashTable)
107 {
108 mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable);
109 }
110
111 // Validate file row changes for keypath-related issues
112 this.ValidateFileRowChanges(mainTransform);
113 }
114
115 // Index File table of pairedTransform
116 Table pairedFileTable = pairedTransform.Tables["File"];
117 RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable);
118
119 if (null != mainFileTable)
120 {
121 if (copyFromPatch)
122 {
123 // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
124 mainTransform.Tables.Remove("MsiFileHash");
125 }
126
127 foreach (FileRow mainFileRow in mainFileTable.Rows)
128 {
129 if (RowOperation.Delete == mainFileRow.Operation)
130 {
131 continue;
132 }
133 else if (RowOperation.None == mainFileRow.Operation && !copyToPatch)
134 {
135 continue;
136 }
137
138 WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
139
140 if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
141 {
142 ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6];
143 FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File);
144
145 // If the file is new, we always need to add it to the patch.
146 if (mainFileRow.Operation != RowOperation.Add)
147 {
148 // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
149 if (null == objectField.PreviousData)
150 {
151 if (mainFileRow.Operation == RowOperation.None)
152 {
153 continue;
154 }
155 }
156 else
157 {
158 // TODO: should this entire condition be placed in the binder file manager?
159 if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&
160 !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
161 {
162 // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
163 mainFileRow.Operation = RowOperation.Modify;
164 if (null != pairedFileRow)
165 {
166 // Always patch-added, but never non-compressed.
167 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
168 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
169 pairedFileRow.Fields[6].Modified = true;
170 pairedFileRow.Operation = RowOperation.Modify;
171 }
172 }
173 else
174 {
175 // The File is same. We need mark all the attributes as unchanged.
176 mainFileRow.Operation = RowOperation.None;
177 foreach (Field field in mainFileRow.Fields)
178 {
179 field.Modified = false;
180 }
181
182 if (null != pairedFileRow)
183 {
184 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded;
185 pairedFileRow.Fields[6].Modified = false;
186 pairedFileRow.Operation = RowOperation.None;
187 }
188 continue;
189 }
190 }
191 }
192 else if (null != pairedFileRow) // RowOperation.Add
193 {
194 // Always patch-added, but never non-compressed.
195 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
196 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
197 pairedFileRow.Fields[6].Modified = true;
198 pairedFileRow.Operation = RowOperation.Add;
199 }
200 }
201
202 // index patch files by diskId+fileId
203 int diskId = mainWixFileRow.DiskId;
204
205 RowDictionary<WixFileRow> mediaFileRows;
206 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
207 {
208 mediaFileRows = new RowDictionary<WixFileRow>();
209 patchMediaFileRows.Add(diskId, mediaFileRows);
210 }
211
212 string fileId = mainFileRow.File;
213 WixFileRow patchFileRow = mediaFileRows.Get(fileId);
214 if (copyToPatch)
215 {
216 if (null == patchFileRow)
217 {
218 FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
219 patchActualFileRow.CopyFrom(mainFileRow);
220
221 patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
222 patchFileRow.CopyFrom(mainWixFileRow);
223
224 mediaFileRows.Add(patchFileRow);
225
226 allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right?
227 }
228 else
229 {
230 // TODO: confirm the rest of data is identical?
231
232 // make sure Source is same. Otherwise we are silently ignoring a file.
233 if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase))
234 {
235 Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
236 }
237
238 // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
239 patchFileRow.AppendPreviousDataFrom(mainWixFileRow);
240 }
241 }
242 else
243 {
244 // copy data from the patch back to the transform
245 if (null != patchFileRow)
246 {
247 FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId);
248 for (int i = 0; i < patchFileRow.Fields.Length; i++)
249 {
250 string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString();
251 string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString();
252
253 if (1 == i)
254 {
255 // File.Component_ changes should not come from the shared file rows
256 // that contain the file information as each individual transform might
257 // have different changes (or no changes at all).
258 }
259 // File.Attributes should not changed for binary deltas
260 else if (6 == i)
261 {
262 if (null != patchFileRow.Patch)
263 {
264 // File.Attribute should not change for binary deltas
265 pairedFileRow.Attributes = mainFileRow.Attributes;
266 mainFileRow.Fields[i].Modified = false;
267 }
268 }
269 // File.Sequence is updated in pairedTransform, not mainTransform
270 else if (7 == i)
271 {
272 // file sequence is updated in Patch table instead of File table for delta patches
273 if (null != patchFileRow.Patch)
274 {
275 pairedFileRow.Fields[i].Modified = false;
276 }
277 else
278 {
279 pairedFileRow[i] = patchFileRow[i];
280 pairedFileRow.Fields[i].Modified = true;
281 }
282 mainFileRow.Fields[i].Modified = false;
283 }
284 else if (patchValue != mainValue)
285 {
286 mainFileRow[i] = patchFileRow[i];
287 mainFileRow.Fields[i].Modified = true;
288 if (mainFileRow.Operation == RowOperation.None)
289 {
290 mainFileRow.Operation = RowOperation.Modify;
291 }
292 }
293 }
294
295 // copy MsiFileHash row for this File
296 Row patchHashRow;
297 if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow))
298 {
299 patchHashRow = patchFileRow.Hash;
300 }
301
302 if (null != patchHashRow)
303 {
304 Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
305 Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
306 for (int i = 0; i < patchHashRow.Fields.Length; i++)
307 {
308 mainHashRow[i] = patchHashRow[i];
309 if (i > 1)
310 {
311 // assume all hash fields have been modified
312 mainHashRow.Fields[i].Modified = true;
313 }
314 }
315
316 // assume the MsiFileHash operation follows the File one
317 mainHashRow.Operation = mainFileRow.Operation;
318 }
319
320 // copy MsiAssemblyName rows for this File
321 List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
322 if (null != patchAssemblyNameRows)
323 {
324 Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
325 foreach (Row patchAssemblyNameRow in patchAssemblyNameRows)
326 {
327 // Copy if there isn't an identical modified/added row already in the transform.
328 bool foundMatchingModifiedRow = false;
329 foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows)
330 {
331 if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
332 {
333 foundMatchingModifiedRow = true;
334 break;
335 }
336 }
337
338 if (!foundMatchingModifiedRow)
339 {
340 Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
341 for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
342 {
343 mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
344 }
345
346 // assume value field has been modified
347 mainAssemblyNameRow.Fields[2].Modified = true;
348 mainAssemblyNameRow.Operation = mainFileRow.Operation;
349 }
350 }
351 }
352
353 // Add patch header for this file
354 if (null != patchFileRow.Patch)
355 {
356 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
357 AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
358 AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
359
360 // Add to Patch table
361 Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
362 if (0 == patchTable.Rows.Count)
363 {
364 patchTable.Operation = TableOperation.Add;
365 }
366
367 Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
368 patchRow[0] = patchFileRow.File;
369 patchRow[1] = patchFileRow.Sequence;
370
371 FileInfo patchFile = new FileInfo(patchFileRow.Source);
372 patchRow[2] = (int)patchFile.Length;
373 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
374
375 string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
376 if (MsiInterop.MsiMaxStreamNameLength < streamName.Length)
377 {
378 streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
379 Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
380 if (0 == patchHeadersTable.Rows.Count)
381 {
382 patchHeadersTable.Operation = TableOperation.Add;
383 }
384 Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
385 patchHeadersRow[0] = streamName;
386 patchHeadersRow[1] = patchFileRow.Patch;
387 patchRow[5] = streamName;
388 patchHeadersRow.Operation = RowOperation.Add;
389 }
390 else
391 {
392 patchRow[4] = patchFileRow.Patch;
393 }
394 patchRow.Operation = RowOperation.Add;
395 }
396 }
397 else
398 {
399 // TODO: throw because all transform rows should have made it into the patch
400 }
401 }
402 }
403 }
404
405 if (copyFromPatch)
406 {
407 this.Output.Tables.Remove("Media");
408 this.Output.Tables.Remove("File");
409 this.Output.Tables.Remove("MsiFileHash");
410 this.Output.Tables.Remove("MsiAssemblyName");
411 }
412 }
413 }
414 finally
415 {
416 this.FileManagerCore.ActiveSubStorage = null;
417 }
418#endif
419 this.FileFacades = allFileRows;
420 }
421
422 /// <summary>
423 /// Adds the PatchFiles action to the sequence table if it does not already exist.
424 /// </summary>
425 /// <param name="table">The sequence table to check or modify.</param>
426 /// <param name="mainTransform">The primary authoring transform.</param>
427 /// <param name="pairedTransform">The secondary patch transform.</param>
428 /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
429 private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow)
430 {
431 // Find/add PatchFiles action (also determine sequence for it).
432 // Search mainTransform first, then pairedTransform (pairedTransform overrides).
433 bool hasPatchFilesAction = false;
434 int seqInstallFiles = 0;
435 int seqDuplicateFiles = 0;
436 string tableName = table.ToString();
437
438 TestSequenceTableForPatchFilesAction(
439 mainTransform.Tables[tableName],
440 ref hasPatchFilesAction,
441 ref seqInstallFiles,
442 ref seqDuplicateFiles);
443 TestSequenceTableForPatchFilesAction(
444 pairedTransform.Tables[tableName],
445 ref hasPatchFilesAction,
446 ref seqInstallFiles,
447 ref seqDuplicateFiles);
448 if (!hasPatchFilesAction)
449 {
450 Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
451 if (0 == iesTable.Rows.Count)
452 {
453 iesTable.Operation = TableOperation.Add;
454 }
455
456 Row patchAction = iesTable.CreateRow(null);
457 WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"];
458 int sequence = wixPatchAction.Sequence;
459 // Test for default sequence value's appropriateness
460 if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence))
461 {
462 if (0 != seqDuplicateFiles)
463 {
464 if (seqDuplicateFiles < seqInstallFiles)
465 {
466 throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
467 }
468 else
469 {
470 sequence = (seqDuplicateFiles + seqInstallFiles) / 2;
471 if (seqInstallFiles == sequence || seqDuplicateFiles == sequence)
472 {
473 throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
474 }
475 }
476 }
477 else
478 {
479 sequence = seqInstallFiles + 1;
480 }
481 }
482 patchAction[0] = wixPatchAction.Action;
483 patchAction[1] = wixPatchAction.Condition;
484 patchAction[2] = sequence;
485 patchAction.Operation = RowOperation.Add;
486 }
487 }
488
489 /// <summary>
490 /// Tests sequence table for PatchFiles and associated actions
491 /// </summary>
492 /// <param name="iesTable">The table to test.</param>
493 /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
494 /// <param name="seqInstallFiles">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
495 /// <param name="seqDuplicateFiles">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
496 private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles)
497 {
498 if (null != iesTable)
499 {
500 foreach (Row iesRow in iesTable.Rows)
501 {
502 if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal))
503 {
504 hasPatchFilesAction = true;
505 }
506 if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal))
507 {
508 seqInstallFiles = (int)iesRow.Fields[2].Data;
509 }
510 if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal))
511 {
512 seqDuplicateFiles = (int)iesRow.Fields[2].Data;
513 }
514 }
515 }
516 }
517
518 /// <summary>
519 /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
520 /// </summary>
521 /// <param name="output">The output to validate.</param>
522 private void ValidateFileRowChanges(Output transform)
523 {
524 Table componentTable = transform.Tables["Component"];
525 Table fileTable = transform.Tables["File"];
526
527 // There's no sense validating keypaths if the transform has no component or file table
528 if (componentTable == null || fileTable == null)
529 {
530 return;
531 }
532
533 Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
534
535 // Index the Component table for non-directory & non-registry key paths.
536 foreach (Row row in componentTable.Rows)
537 {
538 if (null != row.Fields[5].Data &&
539 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath))
540 {
541 componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString());
542 }
543 }
544
545 Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>();
546 Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>();
547 // Verify changes in the file table, now that file diffing has occurred
548 foreach (FileRow row in fileTable.Rows)
549 {
550 string fileId = row.Fields[0].Data.ToString();
551 string componentId = row.Fields[1].Data.ToString();
552
553 if (RowOperation.Modify != row.Operation)
554 {
555 continue;
556 }
557
558 // If this file is the keypath of a component
559 if (componentKeyPath.ContainsValue(fileId))
560 {
561 if (!componentWithChangedKeyPath.ContainsKey(componentId))
562 {
563 componentWithChangedKeyPath.Add(componentId, fileId);
564 }
565 }
566 else
567 {
568 if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
569 {
570 componentWithNonKeyPathChanged.Add(componentId, fileId);
571 }
572 }
573 }
574
575 foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged)
576 {
577 // Make sure all changes to non keypath files also had a change in the keypath.
578 if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key))
579 {
580 Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key]));
581 }
582 }
583 }
584
585 private bool CompareFiles(string targetFile, string updatedFile)
586 {
587 bool? compared = null;
588 foreach (var extension in this.Extensions)
589 {
590 compared = extension.CompareFiles(targetFile, updatedFile);
591
592 if (compared.HasValue)
593 {
594 break;
595 }
596 }
597
598 if (!compared.HasValue)
599 {
600 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
601 }
602
603 return compared.Value;
604 }
605 }
606}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
new file mode 100644
index 00000000..02015744
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
@@ -0,0 +1,499 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Threading;
12 using WixToolset.Core.Bind;
13 using WixToolset.Core.WindowsInstaller.Bind;
14 using WixToolset.Data;
15 using WixToolset.Data.Bind;
16 using WixToolset.Data.Rows;
17 using WixToolset.Extensibility;
18
19 /// <summary>
20 /// Creates cabinet files.
21 /// </summary>
22 internal class CreateCabinetsCommand
23 {
24 public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
25 public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
26
27 private List<FileTransfer> fileTransfers;
28
29 private FileSplitCabNamesCallback newCabNamesCallBack;
30
31 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
32
33 public CreateCabinetsCommand()
34 {
35 this.fileTransfers = new List<FileTransfer>();
36
37 this.newCabNamesCallBack = this.NewCabNamesCallBack;
38 }
39
40 /// <summary>
41 /// Sets the number of threads to use for cabinet creation.
42 /// </summary>
43 public int CabbingThreadCount { private get; set; }
44
45 public string CabCachePath { private get; set; }
46
47 public string TempFilesLocation { private get; set; }
48
49 /// <summary>
50 /// Sets the default compression level to use for cabinets
51 /// that don't have their compression level explicitly set.
52 /// </summary>
53 public CompressionLevel DefaultCompressionLevel { private get; set; }
54
55 public IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { private get; set; }
56
57 public Output Output { private get; set; }
58
59 public string LayoutDirectory { private get; set; }
60
61 public bool Compressed { private get; set; }
62
63 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; }
64
65 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
66
67 public TableDefinitionCollection TableDefinitions { private get; set; }
68
69 public Table WixMediaTable { private get; set; }
70
71 public IEnumerable<FileTransfer> FileTransfers => this.fileTransfers;
72
73 /// <param name="output">Output to generate image for.</param>
74 /// <param name="fileTransfers">Array of files to be transfered.</param>
75 /// <param name="layoutDirectory">The directory in which the image should be layed out.</param>
76 /// <param name="compressed">Flag if source image should be compressed.</param>
77 /// <returns>The uncompressed file rows.</returns>
78 public void Execute()
79 {
80 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
81
82 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
83
84 this.SetCabbingThreadCount();
85
86 // Send Binder object to Facilitate NewCabNamesCallBack Callback
87 CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
88
89 // Supply Compile MediaTemplate Attributes to Cabinet Builder
90 int MaximumCabinetSizeForLargeFileSplitting;
91 int MaximumUncompressedMediaSize;
92 this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize);
93 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting;
94 cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize;
95
96 foreach (var entry in this.FileRowsByCabinet)
97 {
98 MediaRow mediaRow = entry.Key;
99 IEnumerable<FileFacade> files = entry.Value;
100 CompressionLevel compressionLevel = this.DefaultCompressionLevel;
101
102 WixMediaRow wixMediaRow = null;
103 string mediaLayoutFolder = null;
104
105 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
106 {
107 mediaLayoutFolder = wixMediaRow.Layout;
108
109 if (wixMediaRow.CompressionLevel.HasValue)
110 {
111 compressionLevel = wixMediaRow.CompressionLevel.Value;
112 }
113 }
114
115 string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
116
117 CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers);
118 if (null != cabinetWorkItem)
119 {
120 cabinetBuilder.Enqueue(cabinetWorkItem);
121 }
122 }
123
124 // stop processing if an error previously occurred
125 if (Messaging.Instance.EncounteredError)
126 {
127 return;
128 }
129
130 // create queued cabinets with multiple threads
131 cabinetBuilder.CreateQueuedCabinets();
132 if (Messaging.Instance.EncounteredError)
133 {
134 return;
135 }
136 }
137
138 /// <summary>
139 /// Sets the thead count to the number of processors if the current thread count is set to 0.
140 /// </summary>
141 /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks>
142 private void SetCabbingThreadCount()
143 {
144 // default the number of cabbing threads to the number of processors if it wasn't specified
145 if (0 == this.CabbingThreadCount)
146 {
147 string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
148
149 try
150 {
151 if (null != numberOfProcessors)
152 {
153 this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat);
154
155 if (0 >= this.CabbingThreadCount)
156 {
157 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
158 }
159 }
160 else // default to 1 if the environment variable is not set
161 {
162 this.CabbingThreadCount = 1;
163 }
164
165 Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
166 }
167 catch (ArgumentException)
168 {
169 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
170 }
171 catch (FormatException)
172 {
173 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
174 }
175 }
176 }
177
178
179 /// <summary>
180 /// Creates a work item to create a cabinet.
181 /// </summary>
182 /// <param name="output">Output for the current database.</param>
183 /// <param name="cabinetDir">Directory to create cabinet in.</param>
184 /// <param name="mediaRow">MediaRow containing information about the cabinet.</param>
185 /// <param name="fileFacades">Collection of files in this cabinet.</param>
186 /// <param name="fileTransfers">Array of files to be transfered.</param>
187 /// <returns>created CabinetWorkItem object</returns>
188 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers)
189 {
190 CabinetWorkItem cabinetWorkItem = null;
191 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet);
192
193 // check for an empty cabinet
194 if (!fileFacades.Any())
195 {
196 string cabinetName = mediaRow.Cabinet;
197
198 // remove the leading '#' from the embedded cabinet name to make the warning easier to understand
199 if (cabinetName.StartsWith("#", StringComparison.Ordinal))
200 {
201 cabinetName = cabinetName.Substring(1);
202 }
203
204 // If building a patch, remind them to run -p for torch.
205 if (OutputType.Patch == output.Type)
206 {
207 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true));
208 }
209 else
210 {
211 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName));
212 }
213 }
214
215 var cabinetResolver = new CabinetResolver(this.CabCachePath, this.BackendExtensions);
216
217 ResolvedCabinet resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades);
218
219 // create a cabinet work item if it's not being skipped
220 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
221 {
222 int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet).
223
224 cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/);
225 }
226 else // reuse the cabinet from the cabinet cache.
227 {
228 Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path));
229
230 try
231 {
232 // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The
233 // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that
234 // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from
235 // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output)
236 // causing the project to look like it perpetually needs a rebuild until all of the reused
237 // cabinets get newer timestamps.
238 File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now);
239 }
240 catch (Exception e)
241 {
242 Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message));
243 }
244 }
245
246 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
247 {
248 Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]);
249
250 Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers);
251 streamRow[0] = mediaRow.Cabinet.Substring(1);
252 streamRow[1] = resolvedCabinet.Path;
253 }
254 else
255 {
256 string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet);
257 FileTransfer transfer;
258 if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer))
259 {
260 transfer.Built = true;
261 fileTransfers.Add(transfer);
262 }
263 }
264
265 return cabinetWorkItem;
266 }
267
268 //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
269 //{
270 // ResolvedCabinet resolved = null;
271
272 // List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
273
274 // foreach (var extension in this.BackendExtensions)
275 // {
276 // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath);
277 // if (null != resolved)
278 // {
279 // break;
280 // }
281 // }
282
283 // return resolved;
284 //}
285
286 /// <summary>
287 /// Delegate for Cabinet Split Callback
288 /// </summary>
289 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
290 internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
291
292 /// <summary>
293 /// Call back to Add File Transfer for new Cab and add new Cab to Media table
294 /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
295 /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
296 /// </summary>
297 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
298 /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
299 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
300 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
301 {
302 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
303 Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
304 try
305 {
306 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
307 {
308 // Cound not get the Lock
309 Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel());
310 mutex.WaitOne(); // Wait on other thread
311 }
312
313 string firstCabinetName = firstCabName + ".cab";
314 string newCabinetName = newCabName;
315 bool transferAdded = false; // Used for Error Handling
316
317 // Create File Transfer for new Cabinet using transfer of Base Cabinet
318 foreach (FileTransfer transfer in this.FileTransfers)
319 {
320 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
321 {
322 string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
323 string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
324
325 FileTransfer newTransfer;
326 if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer))
327 {
328 newTransfer.Built = true;
329 this.fileTransfers.Add(newTransfer);
330 transferAdded = true;
331 break;
332 }
333 }
334 }
335
336 // Check if File Transfer was added
337 if (!transferAdded)
338 {
339 throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
340 }
341
342 // Add the new Cabinets to media table using LastSequence of Base Cabinet
343 Table mediaTable = this.Output.Tables["Media"];
344 Table wixFileTable = this.Output.Tables["WixFile"];
345 int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
346 int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
347 bool lastSplitCabinetFound = false; // Used for Error Handling
348
349 string lastCabinetOfThisSequence = String.Empty;
350 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
351 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
352 {
353 // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
354 lastCabinetOfThisSequence = firstCabinetName;
355 }
356
357 foreach (MediaRow mediaRow in mediaTable.Rows)
358 {
359 // Get details for the Last Cabinet Added in this Split Sequence
360 if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
361 {
362 lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
363 diskIDForLastSplitCabAdded = mediaRow.DiskId;
364 lastSplitCabinetFound = true;
365 }
366
367 // Check for Name Collision for the new Cabinet added
368 if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
369 {
370 // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
371 throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
372 }
373 }
374
375 // Check if the last Split Cabinet was found in the Media Table
376 if (!lastSplitCabinetFound)
377 {
378 throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
379 }
380
381 // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
382 // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
383 // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
384 MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
385 newMediaRow.Cabinet = newCabinetName;
386 newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
387 newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
388
389 // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
390 foreach (MediaRow mediaRow in mediaTable.Rows)
391 {
392 // Check if this row comes after inserted row and it is not the new cabinet inserted row
393 if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
394 {
395 mediaRow.DiskId++; // Increment DiskID
396 }
397 }
398
399 // Now Increment DiskID for All files Rows so that they refer to the right Media Row
400 foreach (WixFileRow wixFileRow in wixFileTable.Rows)
401 {
402 // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
403 // This check will work as we have only one large file in every splitting cabinet
404 // If we want to support splitting cabinet with more large files we need to update this code
405 if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
406 {
407 wixFileRow.DiskId++; // Increment DiskID
408 }
409 }
410
411 // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
412 this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
413
414 mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
415 }
416 finally
417 {
418 // Releasing the Mutex here
419 mutex.ReleaseMutex();
420 }
421 }
422
423
424 /// <summary>
425 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
426 /// </summary>
427 /// <param name="output">Output to generate image for.</param>
428 /// <param name="fileRows">The indexed file rows.</param>
429 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
430 {
431 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
432 string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
433 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
434 int maxCabSizeForLargeFileInMB = 0;
435 int maxPreCompressedSizeInMB = 0;
436 ulong testOverFlow = 0;
437
438 // Supply Compile MediaTemplate Attributes to Cabinet Builder
439 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
440 if (mediaTemplateTable != null)
441 {
442 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
443
444 // Get the Value for Max Cab Size for File Splitting
445 try
446 {
447 // Override authored mcslfs value if environment variable is authored.
448 if (!String.IsNullOrEmpty(mcslfsString))
449 {
450 maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString);
451 }
452 else
453 {
454 maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting;
455 }
456 testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
457 }
458 catch (FormatException)
459 {
460 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString));
461 }
462 catch (OverflowException)
463 {
464 throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting));
465 }
466
467 try
468 {
469 // Override authored mums value if environment variable is authored.
470 if (!String.IsNullOrEmpty(mumsString))
471 {
472 maxPreCompressedSizeInMB = Int32.Parse(mumsString);
473 }
474 else
475 {
476 maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
477 }
478 testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
479 }
480 catch (FormatException)
481 {
482 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
483 }
484 catch (OverflowException)
485 {
486 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB));
487 }
488
489 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
490 maxUncompressedMediaSize = maxPreCompressedSizeInMB;
491 }
492 else
493 {
494 maxCabSizeForLargeFileSplitting = 0;
495 maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize;
496 }
497 }
498 }
499}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs
new file mode 100644
index 00000000..767671b8
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs
@@ -0,0 +1,87 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Core.Bind;
10 using WixToolset.Data;
11 using WixToolset.Data.Rows;
12
13 /// <summary>
14 /// Creates delta patches and updates the appropriate rows to point to the newly generated patches.
15 /// </summary>
16 internal class CreateDeltaPatchesCommand
17 {
18 public IEnumerable<FileFacade> FileFacades { private get; set; }
19
20 public Table WixPatchIdTable { private get; set; }
21
22 public string TempFilesLocation { private get; set; }
23
24 public void Execute()
25 {
26 bool optimizePatchSizeForLargeFiles = false;
27 PatchSymbolFlagsType apiPatchingSymbolFlags = 0;
28
29 if (null != this.WixPatchIdTable)
30 {
31 Row row = this.WixPatchIdTable.Rows[0];
32 if (null != row)
33 {
34 if (null != row[2])
35 {
36 optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture));
37 }
38
39 if (null != row[3])
40 {
41 apiPatchingSymbolFlags = (PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture);
42 }
43 }
44 }
45
46 foreach (FileFacade facade in this.FileFacades)
47 {
48 if (RowOperation.Modify == facade.File.Operation &&
49 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile))
50 {
51 string deltaBase = String.Concat("delta_", facade.File.File);
52 string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf"));
53 string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd"));
54
55 bool retainRangeWarning = false;
56
57 if (PatchAPI.PatchInterop.CreateDelta(
58 deltaFile,
59 facade.WixFile.Source,
60 facade.DeltaPatchFile.Symbols,
61 facade.DeltaPatchFile.RetainOffsets,
62 new[] { facade.WixFile.PreviousSource },
63 facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }),
64 facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }),
65 facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }),
66 facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }),
67 facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }),
68 apiPatchingSymbolFlags,
69 optimizePatchSizeForLargeFiles,
70 out retainRangeWarning))
71 {
72 PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile);
73
74 facade.WixFile.Source = deltaFile;
75 facade.WixFile.DeltaPatchHeaderSource = headerFile;
76 }
77
78 if (retainRangeWarning)
79 {
80 // TODO: get patch family to add to warning message for PatchWiz parity.
81 Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File));
82 }
83 }
84 }
85 }
86 }
87}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs
new file mode 100644
index 00000000..aef130b0
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs
@@ -0,0 +1,68 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class CreateSpecialPropertiesCommand
11 {
12 public Table PropertyTable { private get; set; }
13
14 public Table WixPropertyTable { private get; set; }
15
16 public void Execute()
17 {
18 // Create the special properties.
19 if (null != this.WixPropertyTable)
20 {
21 // Create lists of the properties that contribute to the special lists of properties.
22 SortedSet<string> adminProperties = new SortedSet<string>();
23 SortedSet<string> secureProperties = new SortedSet<string>();
24 SortedSet<string> hiddenProperties = new SortedSet<string>();
25
26 foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows)
27 {
28 if (wixPropertyRow.Admin)
29 {
30 adminProperties.Add(wixPropertyRow.Id);
31 }
32
33 if (wixPropertyRow.Hidden)
34 {
35 hiddenProperties.Add(wixPropertyRow.Id);
36 }
37
38 if (wixPropertyRow.Secure)
39 {
40 secureProperties.Add(wixPropertyRow.Id);
41 }
42 }
43
44 Table propertyTable = this.PropertyTable;
45 if (0 < adminProperties.Count)
46 {
47 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
48 row.Property = "AdminProperties";
49 row.Value = String.Join(";", adminProperties);
50 }
51
52 if (0 < secureProperties.Count)
53 {
54 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
55 row.Property = "SecureCustomProperties";
56 row.Value = String.Join(";", secureProperties);
57 }
58
59 if (0 < hiddenProperties.Count)
60 {
61 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
62 row.Property = "MsiHiddenProperties";
63 row.Value = String.Join(";", hiddenProperties);
64 }
65 }
66 }
67 }
68}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
new file mode 100644
index 00000000..ae76037d
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
@@ -0,0 +1,226 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Runtime.InteropServices;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.MergeMod;
15 using WixToolset.Msi;
16 using WixToolset.Core.Native;
17 using WixToolset.Core.Bind;
18 using WixToolset.Core.Cab;
19
20 /// <summary>
21 /// Retrieve files information and extract them from merge modules.
22 /// </summary>
23 internal class ExtractMergeModuleFilesCommand
24 {
25 public IEnumerable<FileFacade> FileFacades { private get; set; }
26
27 public Table FileTable { private get; set; }
28
29 public Table WixFileTable { private get; set; }
30
31 public Table WixMergeTable { private get; set; }
32
33 public int OutputInstallerVersion { private get; set; }
34
35 public bool SuppressLayout { private get; set; }
36
37 public string TempFilesLocation { private get; set; }
38
39 public IEnumerable<FileFacade> MergeModulesFileFacades { get; private set; }
40
41 public void Execute()
42 {
43 List<FileFacade> mergeModulesFileFacades = new List<FileFacade>();
44
45 IMsmMerge2 merge = MsmInterop.GetMsmMerge();
46
47 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
48 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
49 // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out
50 // there are other cases where we need all the file rows indexed, however they are not common cases.
51 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
52 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
53 // is a lot more costly for the common cases.
54 Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal);
55
56 foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows)
57 {
58 bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
59
60 // If the module has files and creating layout
61 if (containsFiles && !this.SuppressLayout)
62 {
63 this.ExtractFilesFromMergeModule(merge, wixMergeRow);
64 }
65 }
66
67 this.MergeModulesFileFacades = mergeModulesFileFacades;
68 }
69
70 private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades)
71 {
72 bool containsFiles = false;
73
74 try
75 {
76 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
77 using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
78 {
79 if (db.TableExists("File") && db.TableExists("Component"))
80 {
81 Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase);
82
83 using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
84 {
85 // add each file row from the merge module into the file row collection (check for errors along the way)
86 while (true)
87 {
88 using (Record record = view.Fetch())
89 {
90 if (null == record)
91 {
92 break;
93 }
94
95 // NOTE: this is very tricky - the merge module file rows are not added to the
96 // file table because they should not be created via idt import. Instead, these
97 // rows are created by merging in the actual modules.
98 FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
99 fileRow.File = record[1];
100 fileRow.Compressed = wixMergeRow.FileCompression;
101
102 WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
103 wixFileRow.Directory = record[2];
104 wixFileRow.DiskId = wixMergeRow.DiskId;
105 wixFileRow.PatchGroup = -1;
106 wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]);
107
108 FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow);
109
110 FileFacade collidingFacade;
111
112 // If case-sensitive collision with another merge module or a user-authored file identifier.
113 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade))
114 {
115 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File));
116 }
117 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
118 {
119 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File));
120 }
121 else // no collision
122 {
123 mergeModulesFileFacades.Add(mergeModuleFileFacade);
124
125 // Keep updating the indexes as new rows are added.
126 indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
127 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
128 }
129
130 containsFiles = true;
131 }
132 }
133 }
134 }
135
136 // Get the summary information to detect the Schema
137 using (SummaryInformation summaryInformation = new SummaryInformation(db))
138 {
139 string moduleInstallerVersionString = summaryInformation.GetProperty(14);
140
141 try
142 {
143 int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
144 if (moduleInstallerVersion > this.OutputInstallerVersion)
145 {
146 Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion));
147 }
148 }
149 catch (FormatException)
150 {
151 throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString));
152 }
153 }
154 }
155 }
156 catch (FileNotFoundException)
157 {
158 throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
159 }
160 catch (Win32Exception)
161 {
162 throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile));
163 }
164
165 return containsFiles;
166 }
167
168 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow)
169 {
170 bool moduleOpen = false;
171 short mergeLanguage;
172
173 try
174 {
175 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
176 }
177 catch (System.FormatException)
178 {
179 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
180 return;
181 }
182
183 try
184 {
185 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
186 moduleOpen = true;
187
188 string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat);
189
190 // extract the module cabinet, then explode all of the files to a temp directory
191 string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab");
192 merge.ExtractCAB(moduleCabPath);
193
194 string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId);
195 Directory.CreateDirectory(mergeIdPath);
196
197 using (var extractCab = new WixExtractCab())
198 {
199 try
200 {
201 extractCab.Extract(moduleCabPath, mergeIdPath);
202 }
203 catch (FileNotFoundException)
204 {
205 throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
206 }
207 catch
208 {
209 throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
210 }
211 }
212 }
213 catch (COMException ce)
214 {
215 throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message));
216 }
217 finally
218 {
219 if (moduleOpen)
220 {
221 merge.CloseModule();
222 }
223 }
224 }
225 }
226}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
new file mode 100644
index 00000000..26d254f2
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
@@ -0,0 +1,332 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Text;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13 using WixToolset.Msi;
14 using WixToolset.Core.Native;
15
16 internal class GenerateDatabaseCommand
17 {
18 public int Codepage { private get; set; }
19
20 public IEnumerable<IBinderExtension> Extensions { private get; set; }
21
22 /// <summary>
23 /// Whether to keep columns added in a transform.
24 /// </summary>
25 public bool KeepAddedColumns { private get; set; }
26
27 public Output Output { private get; set; }
28
29 public string OutputPath { private get; set; }
30
31 public TableDefinitionCollection TableDefinitions { private get; set; }
32
33 public string TempFilesLocation { private get; set; }
34
35 /// <summary>
36 /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.
37 /// </summary>
38 public bool SuppressAddingValidationRows { private get; set; }
39
40 public bool UseSubDirectory { private get; set; }
41
42 public void Execute()
43 {
44 // Add the _Validation rows.
45 if (!this.SuppressAddingValidationRows)
46 {
47 Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]);
48
49 foreach (Table table in this.Output.Tables)
50 {
51 if (!table.Definition.Unreal)
52 {
53 // Add the validation rows for this table.
54 table.Definition.AddValidationRows(validationTable);
55 }
56 }
57 }
58
59 // Set the base directory.
60 string baseDirectory = this.TempFilesLocation;
61
62 if (this.UseSubDirectory)
63 {
64 string filename = Path.GetFileNameWithoutExtension(this.OutputPath);
65 baseDirectory = Path.Combine(baseDirectory, filename);
66
67 // make sure the directory exists
68 Directory.CreateDirectory(baseDirectory);
69 }
70
71 try
72 {
73 OpenDatabase type = OpenDatabase.CreateDirect;
74
75 // set special flag for patch files
76 if (OutputType.Patch == this.Output.Type)
77 {
78 type |= OpenDatabase.OpenPatchFile;
79 }
80
81#if DEBUG
82 Console.WriteLine("Opening database at: {0}", this.OutputPath);
83#endif
84
85 using (Database db = new Database(this.OutputPath, type))
86 {
87 // Localize the codepage if a value was specified directly.
88 if (-1 != this.Codepage)
89 {
90 this.Output.Codepage = this.Codepage;
91 }
92
93 // if we're not using the default codepage, import a new one into our
94 // database before we add any tables (or the tables would be added
95 // with the wrong codepage).
96 if (0 != this.Output.Codepage)
97 {
98 this.SetDatabaseCodepage(db, this.Output.Codepage);
99 }
100
101 foreach (Table table in this.Output.Tables)
102 {
103 Table importTable = table;
104 bool hasBinaryColumn = false;
105
106 // Skip all unreal tables other than _Streams.
107 if (table.Definition.Unreal && "_Streams" != table.Name)
108 {
109 continue;
110 }
111
112 // Do not put the _Validation table in patches, it is not needed.
113 if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name)
114 {
115 continue;
116 }
117
118 // The only way to import binary data is to copy it to a local subdirectory first.
119 // To avoid this extra copying and perf hit, import an empty table with the same
120 // definition and later import the binary data from source using records.
121 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
122 {
123 if (ColumnType.Object == columnDefinition.Type)
124 {
125 importTable = new Table(table.Section, table.Definition);
126 hasBinaryColumn = true;
127 break;
128 }
129 }
130
131 // Create the table via IDT import.
132 if ("_Streams" != importTable.Name)
133 {
134 try
135 {
136 db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns);
137 }
138 catch (WixInvalidIdtException)
139 {
140 // If ValidateRows finds anything it doesn't like, it throws
141 importTable.ValidateRows();
142
143 // Otherwise we rethrow the InvalidIdt
144 throw;
145 }
146 }
147
148 // insert the rows via SQL query if this table contains object fields
149 if (hasBinaryColumn)
150 {
151 StringBuilder query = new StringBuilder("SELECT ");
152
153 // Build the query for the view.
154 bool firstColumn = true;
155 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
156 {
157 if (!firstColumn)
158 {
159 query.Append(",");
160 }
161
162 query.AppendFormat(" `{0}`", columnDefinition.Name);
163 firstColumn = false;
164 }
165 query.AppendFormat(" FROM `{0}`", table.Name);
166
167 using (View tableView = db.OpenExecuteView(query.ToString()))
168 {
169 // Import each row containing a stream
170 foreach (Row row in table.Rows)
171 {
172 using (Record record = new Record(table.Definition.Columns.Count))
173 {
174 StringBuilder streamName = new StringBuilder();
175 bool needStream = false;
176
177 // the _Streams table doesn't prepend the table name (or a period)
178 if ("_Streams" != table.Name)
179 {
180 streamName.Append(table.Name);
181 }
182
183 for (int i = 0; i < table.Definition.Columns.Count; i++)
184 {
185 ColumnDefinition columnDefinition = table.Definition.Columns[i];
186
187 switch (columnDefinition.Type)
188 {
189 case ColumnType.Localized:
190 case ColumnType.Preserved:
191 case ColumnType.String:
192 if (columnDefinition.PrimaryKey)
193 {
194 if (0 < streamName.Length)
195 {
196 streamName.Append(".");
197 }
198 streamName.Append((string)row[i]);
199 }
200
201 record.SetString(i + 1, (string)row[i]);
202 break;
203 case ColumnType.Number:
204 record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture));
205 break;
206 case ColumnType.Object:
207 if (null != row[i])
208 {
209 needStream = true;
210 try
211 {
212 record.SetStream(i + 1, (string)row[i]);
213 }
214 catch (Win32Exception e)
215 {
216 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
217 {
218 throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i]));
219 }
220 else
221 {
222 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
223 }
224 }
225 }
226 break;
227 }
228 }
229
230 // stream names are created by concatenating the name of the table with the values
231 // of the primary key (delimited by periods)
232 // check for a stream name that is more than 62 characters long (the maximum allowed length)
233 if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length)
234 {
235 Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
236 }
237 else // add the row to the database
238 {
239 tableView.Modify(ModifyView.Assign, record);
240 }
241 }
242 }
243 }
244
245 // Remove rows from the _Streams table for wixpdbs.
246 if ("_Streams" == table.Name)
247 {
248 table.Rows.Clear();
249 }
250 }
251 }
252
253 // Insert substorages (usually transforms inside a patch or instance transforms in a package).
254 if (0 < this.Output.SubStorages.Count)
255 {
256 using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
257 {
258 foreach (SubStorage subStorage in this.Output.SubStorages)
259 {
260 string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst"));
261
262 // Bind the transform.
263 this.BindTransform(subStorage.Data, transformFile);
264
265 if (Messaging.Instance.EncounteredError)
266 {
267 continue;
268 }
269
270 // add the storage
271 using (Record record = new Record(2))
272 {
273 record.SetString(1, subStorage.Name);
274 record.SetStream(2, transformFile);
275 storagesView.Modify(ModifyView.Assign, record);
276 }
277 }
278 }
279 }
280
281 // We're good, commit the changes to the new database.
282 db.Commit();
283 }
284 }
285 catch (IOException)
286 {
287 // TODO: this error message doesn't seem specific enough
288 throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath);
289 }
290 }
291
292 private void BindTransform(Output transform, string outputPath)
293 {
294 BindTransformCommand command = new BindTransformCommand();
295 command.Extensions = this.Extensions;
296 command.TempFilesLocation = this.TempFilesLocation;
297 command.Transform = transform;
298 command.OutputPath = outputPath;
299 command.TableDefinitions = this.TableDefinitions;
300 command.Execute();
301 }
302
303 /// <summary>
304 /// Sets the codepage of a database.
305 /// </summary>
306 /// <param name="db">Database to set codepage into.</param>
307 /// <param name="output">Output with the codepage for the database.</param>
308 private void SetDatabaseCodepage(Database db, int codepage)
309 {
310 // write out the _ForceCodepage IDT file
311 string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt");
312 using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII))
313 {
314 idtFile.WriteLine(); // dummy column name record
315 idtFile.WriteLine(); // dummy column definition record
316 idtFile.Write(codepage);
317 idtFile.WriteLine("\t_ForceCodepage");
318 }
319
320 // try to import the table into the MSI
321 try
322 {
323 db.Import(idtPath);
324 }
325 catch (WixInvalidIdtException)
326 {
327 // the IDT should be valid, so an invalid code page was given
328 throw new WixException(WixErrors.IllegalCodepage(codepage));
329 }
330 }
331 }
332}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
new file mode 100644
index 00000000..caf8b7a7
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
@@ -0,0 +1,149 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Core.Bind;
10 using WixToolset.Data;
11 using WixToolset.Data.Rows;
12
13 internal class GetFileFacadesCommand
14 {
15 public Table FileTable { private get; set; }
16
17 public Table WixFileTable { private get; set; }
18
19 public Table WixDeltaPatchFileTable { private get; set; }
20
21 public Table WixDeltaPatchSymbolPathsTable { private get; set; }
22
23 public List<FileFacade> FileFacades { get; private set; }
24
25 public void Execute()
26 {
27 List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count);
28
29 RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable);
30 RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable);
31
32 foreach (FileRow file in this.FileTable.Rows)
33 {
34 WixDeltaPatchFileRow deltaPatchFile = null;
35
36 deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile);
37
38 facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile));
39 }
40
41 if (null != this.WixDeltaPatchSymbolPathsTable)
42 {
43 this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
44 }
45
46 this.FileFacades = facades;
47 }
48
49 /// <summary>
50 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
51 /// </summary>
52 public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades)
53 {
54 ILookup<string, FileFacade> filesByComponent = null;
55 ILookup<string, FileFacade> filesByDirectory = null;
56 ILookup<string, FileFacade> filesByDiskId = null;
57
58 foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type))
59 {
60 switch (row.Type)
61 {
62 case SymbolPathType.File:
63 this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]);
64 break;
65
66 case SymbolPathType.Component:
67 if (null == filesByComponent)
68 {
69 filesByComponent = facades.ToLookup(f => f.File.Component);
70 }
71
72 foreach (FileFacade facade in filesByComponent[row.Id])
73 {
74 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
75 }
76 break;
77
78 case SymbolPathType.Directory:
79 if (null == filesByDirectory)
80 {
81 filesByDirectory = facades.ToLookup(f => f.WixFile.Directory);
82 }
83
84 foreach (FileFacade facade in filesByDirectory[row.Id])
85 {
86 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
87 }
88 break;
89
90 case SymbolPathType.Media:
91 if (null == filesByDiskId)
92 {
93 filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture));
94 }
95
96 foreach (FileFacade facade in filesByDiskId[row.Id])
97 {
98 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
99 }
100 break;
101
102 case SymbolPathType.Product:
103 foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values)
104 {
105 this.MergeSymbolPaths(row, fileRow);
106 }
107 break;
108
109 default:
110 // error
111 break;
112 }
113 }
114
115 return deltaPatchFiles;
116 }
117
118 /// <summary>
119 /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row.
120 /// </summary>
121 /// <param name="row">Row from the WixPatchSymbolsPaths table.</param>
122 /// <param name="file">FileRow into which to set symbol information.</param>
123 /// <comment>This includes PreviousData as well.</comment>
124 private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file)
125 {
126 if (null == file.Symbols)
127 {
128 file.Symbols = row.SymbolPaths;
129 }
130 else
131 {
132 file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths);
133 }
134
135 Field field = row.Fields[2];
136 if (null != field.PreviousData)
137 {
138 if (null == file.PreviousSymbols)
139 {
140 file.PreviousSymbols = field.PreviousData;
141 }
142 else
143 {
144 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
145 }
146 }
147 }
148 }
149}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
new file mode 100644
index 00000000..624cbb43
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
@@ -0,0 +1,351 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.IO;
12 using System.Linq;
13 using System.Runtime.InteropServices;
14 using System.Text;
15 using System.Xml;
16 using System.Xml.XPath;
17 using WixToolset.Clr.Interop;
18 using WixToolset.Data;
19 using WixToolset.Data.Rows;
20 using WixToolset.MergeMod;
21 using WixToolset.Msi;
22 using WixToolset.Core.Native;
23 using WixToolset.Core.Bind;
24
25 /// <summary>
26 /// Update file information.
27 /// </summary>
28 internal class MergeModulesCommand
29 {
30 public IEnumerable<FileFacade> FileFacades { private get; set; }
31
32 public Output Output { private get; set; }
33
34 public string OutputPath { private get; set; }
35
36 public IEnumerable<string> SuppressedTableNames { private get; set; }
37
38 public string TempFilesLocation { private get; set; }
39
40 public void Execute()
41 {
42 Debug.Assert(OutputType.Product == this.Output.Type);
43
44 Table wixMergeTable = this.Output.Tables["WixMerge"];
45 Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"];
46
47 // check for merge rows to see if there is any work to do
48 if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count)
49 {
50 return;
51 }
52
53 IMsmMerge2 merge = null;
54 bool commit = true;
55 bool logOpen = false;
56 bool databaseOpen = false;
57 string logPath = null;
58 try
59 {
60 merge = MsmInterop.GetMsmMerge();
61
62 logPath = Path.Combine(this.TempFilesLocation, "merge.log");
63 merge.OpenLog(logPath);
64 logOpen = true;
65
66 merge.OpenDatabase(this.OutputPath);
67 databaseOpen = true;
68
69 // process all the merge rows
70 foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows)
71 {
72 bool moduleOpen = false;
73
74 try
75 {
76 short mergeLanguage;
77
78 try
79 {
80 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
81 }
82 catch (System.FormatException)
83 {
84 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
85 continue;
86 }
87
88 Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage));
89 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
90 moduleOpen = true;
91
92 // If there is merge configuration data, create a callback object to contain it all.
93 ConfigurationCallback callback = null;
94 if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData))
95 {
96 callback = new ConfigurationCallback(wixMergeRow.ConfigurationData);
97 }
98
99 // merge the module into the database that's being built
100 Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile));
101 merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback);
102
103 // connect any non-primary features
104 if (null != wixFeatureModulesTable)
105 {
106 foreach (Row row in wixFeatureModulesTable.Rows)
107 {
108 if (wixMergeRow.Id == (string)row[1])
109 {
110 Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0]));
111 merge.Connect((string)row[0]);
112 }
113 }
114 }
115 }
116 catch (COMException)
117 {
118 commit = false;
119 }
120 finally
121 {
122 IMsmErrors mergeErrors = merge.Errors;
123
124 // display all the errors encountered during the merge operations for this module
125 for (int i = 1; i <= mergeErrors.Count; i++)
126 {
127 IMsmError mergeError = mergeErrors[i];
128 StringBuilder databaseKeys = new StringBuilder();
129 StringBuilder moduleKeys = new StringBuilder();
130
131 // build a string of the database keys
132 for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++)
133 {
134 if (1 != j)
135 {
136 databaseKeys.Append(';');
137 }
138 databaseKeys.Append(mergeError.DatabaseKeys[j]);
139 }
140
141 // build a string of the module keys
142 for (int j = 1; j <= mergeError.ModuleKeys.Count; j++)
143 {
144 if (1 != j)
145 {
146 moduleKeys.Append(';');
147 }
148 moduleKeys.Append(mergeError.ModuleKeys[j]);
149 }
150
151 // display the merge error based on the msm error type
152 switch (mergeError.Type)
153 {
154 case MsmErrorType.msmErrorExclusion:
155 Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString()));
156 break;
157 case MsmErrorType.msmErrorFeatureRequired:
158 Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id));
159 break;
160 case MsmErrorType.msmErrorLanguageFailed:
161 Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
162 break;
163 case MsmErrorType.msmErrorLanguageUnsupported:
164 Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
165 break;
166 case MsmErrorType.msmErrorResequenceMerge:
167 Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
168 break;
169 case MsmErrorType.msmErrorTableMerge:
170 if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table
171 {
172 Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
173 }
174 break;
175 case MsmErrorType.msmErrorPlatformMismatch:
176 Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
177 break;
178 default:
179 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace));
180 break;
181 }
182 }
183
184 if (0 >= mergeErrors.Count && !commit)
185 {
186 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace));
187 }
188
189 if (moduleOpen)
190 {
191 merge.CloseModule();
192 }
193 }
194 }
195 }
196 finally
197 {
198 if (databaseOpen)
199 {
200 merge.CloseDatabase(commit);
201 }
202
203 if (logOpen)
204 {
205 merge.CloseLog();
206 }
207 }
208
209 // stop processing if an error previously occurred
210 if (Messaging.Instance.EncounteredError)
211 {
212 return;
213 }
214
215 using (Database db = new Database(this.OutputPath, OpenDatabase.Direct))
216 {
217 Table suppressActionTable = this.Output.Tables["WixSuppressAction"];
218
219 // suppress individual actions
220 if (null != suppressActionTable)
221 {
222 foreach (Row row in suppressActionTable.Rows)
223 {
224 if (db.TableExists((string)row[0]))
225 {
226 string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]);
227
228 using (View view = db.OpenExecuteView(query))
229 {
230 using (Record record = view.Fetch())
231 {
232 if (null != record)
233 {
234 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString()));
235 view.Modify(ModifyView.Delete, record);
236 }
237 }
238 }
239 }
240 }
241 }
242
243 // query for merge module actions in suppressed sequences and drop them
244 foreach (string tableName in this.SuppressedTableNames)
245 {
246 if (!db.TableExists(tableName))
247 {
248 continue;
249 }
250
251 using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
252 {
253 while (true)
254 {
255 using (Record resultRecord = view.Fetch())
256 {
257 if (null == resultRecord)
258 {
259 break;
260 }
261
262 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName));
263 }
264 }
265 }
266
267 // drop suppressed sequences
268 using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
269 {
270 }
271
272 // delete the validation rows
273 using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
274 {
275 using (Record record = new Record(1))
276 {
277 record.SetString(1, tableName);
278 view.Execute(record);
279 }
280 }
281 }
282
283 // now update the Attributes column for the files from the Merge Modules
284 Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles());
285 using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
286 {
287 foreach (FileFacade file in this.FileFacades)
288 {
289 if (!file.FromModule)
290 {
291 continue;
292 }
293
294 using (Record record = new Record(1))
295 {
296 record.SetString(1, file.File.File);
297 view.Execute(record);
298 }
299
300 using (Record recordUpdate = view.Fetch())
301 {
302 if (null == recordUpdate)
303 {
304 throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module.");
305 }
306
307 recordUpdate.SetInteger(1, file.File.Sequence);
308
309 // update the file attributes to match the compression specified
310 // on the Merge element or on the Package element
311 int attributes = 0;
312
313 // get the current value if its not null
314 if (!recordUpdate.IsNull(2))
315 {
316 attributes = recordUpdate.GetInteger(2);
317 }
318
319 if (YesNoType.Yes == file.File.Compressed)
320 {
321 // these are mutually exclusive
322 attributes |= MsiInterop.MsidbFileAttributesCompressed;
323 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
324 }
325 else if (YesNoType.No == file.File.Compressed)
326 {
327 // these are mutually exclusive
328 attributes |= MsiInterop.MsidbFileAttributesNoncompressed;
329 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
330 }
331 else // not specified
332 {
333 Debug.Assert(YesNoType.NotSet == file.File.Compressed);
334
335 // clear any compression bits
336 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
337 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
338 }
339
340 recordUpdate.SetInteger(2, attributes);
341
342 view.Modify(ModifyView.Update, recordUpdate);
343 }
344 }
345 }
346
347 db.Commit();
348 }
349 }
350 }
351}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
new file mode 100644
index 00000000..b3c09b9e
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
@@ -0,0 +1,118 @@
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.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Msi;
12 using WixToolset.Core.Native;
13 using WixToolset.Bind;
14 using WixToolset.Core.Bind;
15 using WixToolset.Data.Bind;
16
17 /// <summary>
18 /// Defines the file transfers necessary to layout the uncompressed files.
19 /// </summary>
20 internal class ProcessUncompressedFilesCommand
21 {
22 public string DatabasePath { private get; set; }
23
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public RowDictionary<MediaRow> MediaRows { private get; set; }
27
28 public string LayoutDirectory { private get; set; }
29
30 public bool Compressed { private get; set; }
31
32 public bool LongNamesInImage { private get; set; }
33
34 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
35
36 public Table WixMediaTable { private get; set; }
37
38 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
39
40 public void Execute()
41 {
42 List<FileTransfer> fileTransfers = new List<FileTransfer>();
43
44 Hashtable directories = new Hashtable();
45
46 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
47
48 using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
49 {
50 using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
51 {
52 while (true)
53 {
54 using (Record directoryRecord = directoryView.Fetch())
55 {
56 if (null == directoryRecord)
57 {
58 break;
59 }
60
61 string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
62
63 directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName));
64 }
65 }
66 }
67
68 using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?"))
69 {
70 using (Record fileQueryRecord = new Record(1))
71 {
72 // for each file in the array of uncompressed files
73 foreach (FileFacade facade in this.FileFacades)
74 {
75 MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId);
76 string relativeFileLayoutPath = null;
77
78 WixMediaRow wixMediaRow = null;
79 string mediaLayoutFolder = null;
80
81 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
82 {
83 mediaLayoutFolder = wixMediaRow.Layout;
84 }
85
86 string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
87
88 // setup up the query record and find the appropriate file in the
89 // previously executed file view
90 fileQueryRecord[1] = facade.File.File;
91 fileView.Execute(fileQueryRecord);
92
93 using (Record fileRecord = fileView.Fetch())
94 {
95 if (null == fileRecord)
96 {
97 throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File));
98 }
99
100 relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
101 }
102
103 // finally put together the base media layout path and the relative file layout path
104 string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
105 FileTransfer transfer;
106 if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer))
107 {
108 fileTransfers.Add(transfer);
109 }
110 }
111 }
112 }
113 }
114
115 this.FileTransfers = fileTransfers;
116 }
117 }
118}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs
new file mode 100644
index 00000000..7da32206
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs
@@ -0,0 +1,80 @@
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.Databases
4{
5 using System;
6 using System.IO;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class UpdateControlTextCommand
11 {
12 public Table BBControlTable { private get; set; }
13
14 public Table WixBBControlTable { private get; set; }
15
16 public Table ControlTable { private get; set; }
17
18 public Table WixControlTable { private get; set; }
19
20 public void Execute()
21 {
22 if (null != this.WixBBControlTable)
23 {
24 RowDictionary<BBControlRow> bbControlRows = new RowDictionary<BBControlRow>(this.BBControlTable);
25 foreach (Row wixRow in this.WixBBControlTable.Rows)
26 {
27 BBControlRow bbControlRow = bbControlRows.Get(wixRow.GetPrimaryKey());
28 bbControlRow.Text = this.ReadTextFile(bbControlRow.SourceLineNumbers, wixRow.FieldAsString(2));
29 }
30 }
31
32 if (null != this.WixControlTable)
33 {
34 RowDictionary<ControlRow> controlRows = new RowDictionary<ControlRow>(this.ControlTable);
35 foreach (Row wixRow in this.WixControlTable.Rows)
36 {
37 ControlRow controlRow = controlRows.Get(wixRow.GetPrimaryKey());
38 controlRow.Text = this.ReadTextFile(controlRow.SourceLineNumbers, wixRow.FieldAsString(2));
39 }
40 }
41 }
42
43 /// <summary>
44 /// Reads a text file and returns the contents.
45 /// </summary>
46 /// <param name="sourceLineNumbers">Source line numbers for row from source.</param>
47 /// <param name="source">Source path to file to read.</param>
48 /// <returns>Text string read from file.</returns>
49 private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source)
50 {
51 string text = null;
52
53 try
54 {
55 using (StreamReader reader = new StreamReader(source))
56 {
57 text = reader.ReadToEnd();
58 }
59 }
60 catch (DirectoryNotFoundException e)
61 {
62 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
63 }
64 catch (FileNotFoundException e)
65 {
66 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
67 }
68 catch (IOException e)
69 {
70 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
71 }
72 catch (NotSupportedException)
73 {
74 Messaging.Instance.OnMessage(WixErrors.FileNotFound(sourceLineNumbers, source));
75 }
76
77 return text;
78 }
79 }
80}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
new file mode 100644
index 00000000..cd9444ee
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
@@ -0,0 +1,533 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Xml;
13 using System.Xml.XPath;
14 using WixToolset.Clr.Interop;
15 using WixToolset.Core.Bind;
16 using WixToolset.Data;
17 using WixToolset.Data.Rows;
18 using WixToolset.Msi;
19
20 /// <summary>
21 /// Update file information.
22 /// </summary>
23 internal class UpdateFileFacadesCommand
24 {
25 public IEnumerable<FileFacade> FileFacades { private get; set; }
26
27 public IEnumerable<FileFacade> UpdateFileFacades { private get; set; }
28
29 public string ModularizationGuid { private get; set; }
30
31 public Output Output { private get; set; }
32
33 public bool OverwriteHash { private get; set; }
34
35 public TableDefinitionCollection TableDefinitions { private get; set; }
36
37 public IDictionary<string, string> VariableCache { private get; set; }
38
39 public void Execute()
40 {
41 foreach (FileFacade file in this.UpdateFileFacades)
42 {
43 this.UpdateFileFacade(file);
44 }
45 }
46
47 private void UpdateFileFacade(FileFacade file)
48 {
49 FileInfo fileInfo = null;
50 try
51 {
52 fileInfo = new FileInfo(file.WixFile.Source);
53 }
54 catch (ArgumentException)
55 {
56 Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
57 return;
58 }
59 catch (PathTooLongException)
60 {
61 Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
62 return;
63 }
64 catch (NotSupportedException)
65 {
66 Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
67 return;
68 }
69
70 if (!fileInfo.Exists)
71 {
72 Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source));
73 return;
74 }
75
76 using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
77 {
78 if (Int32.MaxValue < fileStream.Length)
79 {
80 throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source));
81 }
82
83 file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
84 }
85
86 string version = null;
87 string language = null;
88 try
89 {
90 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
91 }
92 catch (Win32Exception e)
93 {
94 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
95 {
96 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
97 }
98 else
99 {
100 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
101 }
102 }
103
104 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
105 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
106 {
107 if (!this.OverwriteHash)
108 {
109 // not overwriting hash, so don't do the rest of these options.
110 }
111 else if (null != file.File.Version)
112 {
113 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
114 // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up.
115 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
116 // all the file rows) for a relatively uncommon situation. Let's not do that.
117 //
118 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
119 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
120 if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal)))
121 {
122 Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File));
123 }
124 }
125 else
126 {
127 if (null != file.File.Language)
128 {
129 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
130 }
131
132 int[] hash;
133 try
134 {
135 Installer.GetFileHash(fileInfo.FullName, 0, out hash);
136 }
137 catch (Win32Exception e)
138 {
139 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
140 {
141 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
142 }
143 else
144 {
145 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
146 }
147 }
148
149 if (null == file.Hash)
150 {
151 Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]);
152 file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers);
153 }
154
155 file.Hash[0] = file.File.File;
156 file.Hash[1] = 0;
157 file.Hash[2] = hash[0];
158 file.Hash[3] = hash[1];
159 file.Hash[4] = hash[2];
160 file.Hash[5] = hash[3];
161 }
162 }
163 else // update the file row with the version and language information.
164 {
165 // If no version was provided by the user, use the version from the file itself.
166 // This is the most common case.
167 if (String.IsNullOrEmpty(file.File.Version))
168 {
169 file.File.Version = version;
170 }
171 else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
172 {
173 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
174 // the version value). We didn't find it so, we will override the default version they provided with the actual
175 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
176 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
177 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
178 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
179 //
180 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
181 // That's typically even more rare than companion files so again, no index, just search.
182 file.File.Version = version;
183 }
184
185 if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language))
186 {
187 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
188 }
189 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
190 {
191 file.File.Language = language;
192 }
193
194 // Populate the binder variables for this file information if requested.
195 if (null != this.VariableCache)
196 {
197 if (!String.IsNullOrEmpty(file.File.Version))
198 {
199 string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File));
200 this.VariableCache[key] = file.File.Version;
201 }
202
203 if (!String.IsNullOrEmpty(file.File.Language))
204 {
205 string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", Common.Demodularize(this.Output.Type, ModularizationGuid, file.File.File));
206 this.VariableCache[key] = file.File.Language;
207 }
208 }
209 }
210
211 // If this is a CLR assembly, load the assembly and get the assembly name information
212 if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType)
213 {
214 bool targetNetfx1 = false;
215 StringDictionary assemblyNameValues = new StringDictionary();
216
217 ClrInterop.IReferenceIdentity referenceIdentity = null;
218 Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid;
219 uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity);
220 if (0 == result && null != referenceIdentity)
221 {
222 string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion");
223 if (null != imageRuntimeVersion)
224 {
225 targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase);
226 }
227
228 string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral";
229 assemblyNameValues.Add("Culture", culture);
230
231 string name = referenceIdentity.GetAttribute(null, "Name");
232 if (null != name)
233 {
234 assemblyNameValues.Add("Name", name);
235 }
236
237 string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture");
238 if (null != processorArchitecture)
239 {
240 assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture);
241 }
242
243 string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken");
244 if (null != publicKeyToken)
245 {
246 bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase));
247
248 // Managed code expects "null" instead of "neutral", and
249 // this won't be installed to the GAC since it's not signed anyway.
250 assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant());
251 assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken);
252 }
253 else if (file.WixFile.AssemblyApplication == null)
254 {
255 throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component));
256 }
257
258 string assemblyVersion = referenceIdentity.GetAttribute(null, "Version");
259 if (null != version)
260 {
261 assemblyNameValues.Add("Version", assemblyVersion);
262 }
263 }
264 else
265 {
266 Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result)));
267 return;
268 }
269
270 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
271 if (assemblyNameValues.ContainsKey("name"))
272 {
273 this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]);
274 }
275
276 if (!String.IsNullOrEmpty(version))
277 {
278 this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version);
279 }
280
281 if (assemblyNameValues.ContainsKey("version"))
282 {
283 string assemblyVersion = assemblyNameValues["version"];
284
285 if (!targetNetfx1)
286 {
287 // There is a bug in v1 fusion that requires the assembly's "version" attribute
288 // to be equal to or longer than the "fileVersion" in length when its present;
289 // the workaround is to prepend zeroes to the last version number in the assembly
290 // version.
291 if (null != version && version.Length > assemblyVersion.Length)
292 {
293 string padding = new string('0', version.Length - assemblyVersion.Length);
294 string[] assemblyVersionNumbers = assemblyVersion.Split('.');
295
296 if (assemblyVersionNumbers.Length > 0)
297 {
298 assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]);
299 assemblyVersion = String.Join(".", assemblyVersionNumbers);
300 }
301 }
302 }
303
304 this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion);
305 }
306
307 if (assemblyNameValues.ContainsKey("culture"))
308 {
309 this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]);
310 }
311
312 if (assemblyNameValues.ContainsKey("publicKeyToken"))
313 {
314 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]);
315 }
316
317 if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
318 {
319 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
320 }
321
322 if (assemblyNameValues.ContainsKey("processorArchitecture"))
323 {
324 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]);
325 }
326
327 // add the assembly name to the information cache
328 if (null != this.VariableCache)
329 {
330 string fileId = Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File);
331 string key = String.Concat("assemblyfullname.", fileId);
332 string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]);
333 if (assemblyNameValues.ContainsKey("processorArchitecture"))
334 {
335 assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]);
336 }
337
338 this.VariableCache[key] = assemblyName;
339
340 // Add entries with the preserved case publicKeyToken
341 string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId);
342 this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]);
343
344 string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId);
345 this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"];
346 }
347 }
348 else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType)
349 {
350 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
351 // all files like this. Even though this is a rare case it looks like we might be able to index the
352 // file earlier.
353 FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal));
354 if (null == fileManifest)
355 {
356 Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest));
357 }
358
359 string win32Type = null;
360 string win32Name = null;
361 string win32Version = null;
362 string win32ProcessorArchitecture = null;
363 string win32PublicKeyToken = null;
364
365 // loading the dom is expensive we want more performant APIs than the DOM
366 // Navigator is cheaper than dom. Perhaps there is a cheaper API still.
367 try
368 {
369 XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source);
370 XPathNavigator nav = doc.CreateNavigator();
371 nav.MoveToRoot();
372
373 // this assumes a particular schema for a win32 manifest and does not
374 // provide error checking if the file does not conform to schema.
375 // The fallback case here is that nothing is added to the MsiAssemblyName
376 // table for an out of tolerance Win32 manifest. Perhaps warnings needed.
377 if (nav.MoveToFirstChild())
378 {
379 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly")
380 {
381 nav.MoveToNext();
382 }
383
384 if (nav.MoveToFirstChild())
385 {
386 bool hasNextSibling = true;
387 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling)
388 {
389 hasNextSibling = nav.MoveToNext();
390 }
391 if (!hasNextSibling)
392 {
393 Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source));
394 return;
395 }
396
397 if (nav.MoveToAttribute("type", String.Empty))
398 {
399 win32Type = nav.Value;
400 nav.MoveToParent();
401 }
402
403 if (nav.MoveToAttribute("name", String.Empty))
404 {
405 win32Name = nav.Value;
406 nav.MoveToParent();
407 }
408
409 if (nav.MoveToAttribute("version", String.Empty))
410 {
411 win32Version = nav.Value;
412 nav.MoveToParent();
413 }
414
415 if (nav.MoveToAttribute("processorArchitecture", String.Empty))
416 {
417 win32ProcessorArchitecture = nav.Value;
418 nav.MoveToParent();
419 }
420
421 if (nav.MoveToAttribute("publicKeyToken", String.Empty))
422 {
423 win32PublicKeyToken = nav.Value;
424 nav.MoveToParent();
425 }
426 }
427 }
428 }
429 catch (FileNotFoundException fe)
430 {
431 Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest"));
432 }
433 catch (XmlException xe)
434 {
435 Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message));
436 }
437
438 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
439 if (!String.IsNullOrEmpty(win32Name))
440 {
441 this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name);
442 }
443
444 if (!String.IsNullOrEmpty(win32Version))
445 {
446 this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version);
447 }
448
449 if (!String.IsNullOrEmpty(win32Type))
450 {
451 this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type);
452 }
453
454 if (!String.IsNullOrEmpty(win32ProcessorArchitecture))
455 {
456 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture);
457 }
458
459 if (!String.IsNullOrEmpty(win32PublicKeyToken))
460 {
461 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken);
462 }
463 }
464 }
465
466 /// <summary>
467 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
468 /// create a new row.
469 /// </summary>
470 /// <param name="assemblyNameTable">MsiAssemblyName table.</param>
471 /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
472 /// <param name="name">MsiAssemblyName name.</param>
473 /// <param name="value">MsiAssemblyName value.</param>
474 private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value)
475 {
476 // check for null value (this can occur when grabbing the file version from an assembly without one)
477 if (String.IsNullOrEmpty(value))
478 {
479 Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name));
480 }
481 else
482 {
483 Row assemblyNameRow = null;
484
485 // override directly authored value
486 foreach (Row row in assemblyNameTable.Rows)
487 {
488 if ((string)row[0] == file.File.Component && (string)row[1] == name)
489 {
490 assemblyNameRow = row;
491 break;
492 }
493 }
494
495 // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
496 if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType &&
497 String.IsNullOrEmpty(file.WixFile.AssemblyApplication) &&
498 !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase))
499 {
500 Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value));
501 }
502
503 if (null == assemblyNameRow)
504 {
505 assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers);
506 assemblyNameRow[0] = file.File.Component;
507 assemblyNameRow[1] = name;
508 assemblyNameRow[2] = value;
509
510 // put the MsiAssemblyName row in the same section as the related File row
511 assemblyNameRow.SectionId = file.File.SectionId;
512
513 if (null == file.AssemblyNames)
514 {
515 file.AssemblyNames = new List<Row>();
516 }
517
518 file.AssemblyNames.Add(assemblyNameRow);
519 }
520 else
521 {
522 assemblyNameRow[2] = value;
523 }
524
525 if (this.VariableCache != null)
526 {
527 string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant();
528 this.VariableCache[key] = (string)assemblyNameRow[2];
529 }
530 }
531 }
532 }
533}