@joebigelow / wix-1 / commits / 6ff680e3

Start on new patch infrastructure

Rob Mensching committed Jan 24, 2020 at 15:27 UTC 6ff680e386b1543ad1a58d1b1d465ce8aa20bc7d
58 files changed +4481 -1192
src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
-1
@@ -208,7 +208,6 @@ namespace WixToolset.Core.Burn
208 variableCache.Add(String.Concat("packageManufacturer.", facade.PackageId), msiPackage.Manufacturer);
209 }
210 }
211 -
211 }
212 break;
213
src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs
+2 -2
@@ -113,7 +113,7 @@ namespace WixToolset.Core.Burn.Bundles
113 ++attachedContainerIndex;
114 }
115
116 - this.CreateContainer(container, containerPayloads, null);
116 + this.CreateContainer(container, containerPayloads);
117 }
118 }
119
@@ -122,7 +122,7 @@ namespace WixToolset.Core.Burn.Bundles
122 this.FileTransfers = fileTransfers;
123 }
124
125 - private void CreateContainer(WixBundleContainerTuple container, IEnumerable<WixBundlePayloadTuple> containerPayloads, string manifestFile)
125 + private void CreateContainer(WixBundleContainerTuple container, IEnumerable<WixBundlePayloadTuple> containerPayloads)
126 {
127 var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel);
128 command.Execute();
src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
+14 -14
@@ -57,7 +57,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
57
58 var mediaRows = new Dictionary<int, MediaTuple>();
59
60 - List<FileFacade> uncompressedFiles = new List<FileFacade>();
60 + var uncompressedFiles = new List<FileFacade>();
61
62 var mediaTable = this.Section.Tuples.OfType<MediaTuple>().ToList();
63 var mediaTemplateTable = this.Section.Tuples.OfType<WixMediaTemplateTuple>().ToList();
@@ -109,8 +109,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
109
110 ulong currentPreCabSize = 0;
111 ulong maxPreCabSizeInBytes;
112 - int maxPreCabSizeInMB = 0;
113 - int currentCabIndex = 0;
112 + var maxPreCabSizeInMB = 0;
113 + var currentCabIndex = 0;
114
115 MediaTuple currentMediaRow = null;
116
@@ -131,7 +131,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
131 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
132 }
133
134 - string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
134 + var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
135
136 try
137 {
@@ -170,13 +170,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
170 {
171 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
172 var cabinetFiles = filesByCabinetMedia[currentMediaRow];
173 - facade.File.DiskId = currentCabIndex;
173 + facade.DiskId = currentCabIndex;
174 cabinetFiles.Add(facade);
175 continue;
176 }
177
178 // Update current cab size.
179 - currentPreCabSize += (ulong)facade.File.FileSize;
179 + currentPreCabSize += (ulong)facade.FileSize;
180
181 if (currentPreCabSize > maxPreCabSizeInBytes)
182 {
@@ -186,10 +186,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
186 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
187
188 var cabinetFileRows = filesByCabinetMedia[currentMediaRow];
189 - facade.File.DiskId = currentCabIndex;
189 + facade.DiskId = currentCabIndex;
190 cabinetFileRows.Add(facade);
191 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
192 - currentPreCabSize = (ulong)facade.File.FileSize;
192 + currentPreCabSize = (ulong)facade.FileSize;
193 }
194 else
195 {
@@ -204,7 +204,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
204
205 // Associate current file with current cab.
206 var cabinetFiles = filesByCabinetMedia[currentMediaRow];
207 - facade.File.DiskId = currentCabIndex;
207 + facade.DiskId = currentCabIndex;
208 cabinetFiles.Add(facade);
209 }
210 }
@@ -260,18 +260,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
260 }
261 }
262
263 - foreach (FileFacade facade in fileFacades)
263 + foreach (var facade in fileFacades)
264 {
265 if (!mediaRows.TryGetValue(facade.DiskId, out var mediaRow))
266 {
267 - this.Messaging.Write(ErrorMessages.MissingMedia(facade.File.SourceLineNumbers, facade.DiskId));
267 + this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId));
268 continue;
269 }
270
271 // When building a product, if the current file is to be uncompressed or if
272 // the package set not to be compressed, don't cab it.
273 - var compressed = (facade.File.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed;
274 - var uncompressed = (facade.File.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed;
273 + var compressed = facade.Compressed;
274 + var uncompressed = facade.Uncompressed;
275 if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed)))
276 {
277 uncompressedFiles.Add(facade);
@@ -284,7 +284,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
284 }
285 else
286 {
287 - this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.Id.Id, facade.DiskId));
287 + this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId));
288 }
289 }
290 }
src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs new
+1322
@@ -0,0 +1,1322 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolset.Core.WindowsInstaller.Bind
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Globalization;
8 + using System.Linq;
9 + using System.Text.RegularExpressions;
10 + using WixToolset.Core.WindowsInstaller;
11 + using WixToolset.Core.WindowsInstaller.Msi;
12 + using WixToolset.Data;
13 + using WixToolset.Data.Tuples;
14 + using WixToolset.Data.WindowsInstaller;
15 + using WixToolset.Data.WindowsInstaller.Rows;
16 + using WixToolset.Extensibility.Services;
17 +
18 + /// <summary>
19 + /// Include transforms in a patch.
20 + /// </summary>
21 + internal class AttachPatchTransformsCommand
22 + {
23 + private static readonly string[] PatchUninstallBreakingTables = new[]
24 + {
25 + "AppId",
26 + "BindImage",
27 + "Class",
28 + "Complus",
29 + "CreateFolder",
30 + "DuplicateFile",
31 + "Environment",
32 + "Extension",
33 + "Font",
34 + "IniFile",
35 + "IsolatedComponent",
36 + "LockPermissions",
37 + "MIME",
38 + "MoveFile",
39 + "MsiLockPermissionsEx",
40 + "MsiServiceConfig",
41 + "MsiServiceConfigFailureActions",
42 + "ODBCAttribute",
43 + "ODBCDataSource",
44 + "ODBCDriver",
45 + "ODBCSourceAttribute",
46 + "ODBCTranslator",
47 + "ProgId",
48 + "PublishComponent",
49 + "RemoveIniFile",
50 + "SelfReg",
51 + "ServiceControl",
52 + "ServiceInstall",
53 + "TypeLib",
54 + "Verb",
55 + };
56 +
57 + private readonly TableDefinitionCollection tableDefinitions;
58 +
59 + public AttachPatchTransformsCommand(IMessaging messaging, Intermediate intermediate, IEnumerable<PatchTransform> transforms)
60 + {
61 + this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandardInternal.GetTableDefinitions());
62 + this.Messaging = messaging;
63 + this.Intermediate = intermediate;
64 + this.Transforms = transforms;
65 + }
66 +
67 + private IMessaging Messaging { get; }
68 +
69 + private Intermediate Intermediate { get; }
70 +
71 + private IEnumerable<PatchTransform> Transforms { get; }
72 +
73 + public IEnumerable<SubStorage> SubStorages { get; private set; }
74 +
75 + public IEnumerable<SubStorage> Execute()
76 + {
77 + var subStorages = new List<SubStorage>();
78 +
79 + if (this.Transforms == null || !this.Transforms.Any())
80 + {
81 + this.Messaging.Write(ErrorMessages.PatchWithoutTransforms());
82 + return subStorages;
83 + }
84 +
85 + var summaryInfo = this.ExtractPatchSummaryInfo();
86 +
87 + var section = this.Intermediate.Sections.First();
88 +
89 + var tuples = this.Intermediate.Sections.SelectMany(s => s.Tuples).ToList();
90 +
91 + // Get the patch id from the WixPatchId tuple.
92 + var patchIdTuple = tuples.OfType<WixPatchIdTuple>().FirstOrDefault();
93 +
94 + if (String.IsNullOrEmpty(patchIdTuple.Id?.Id))
95 + {
96 + this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp());
97 + return subStorages;
98 + }
99 +
100 + if (String.IsNullOrEmpty(patchIdTuple.ClientPatchId))
101 + {
102 + this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp());
103 + return subStorages;
104 + }
105 +
106 + // enumerate patch.Media to map diskId to Media row
107 + var patchMediaByDiskId = tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
108 +
109 + if (patchMediaByDiskId.Count == 0)
110 + {
111 + this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp());
112 + return subStorages;
113 + }
114 +
115 + // populate MSP summary information
116 + var patchMetadata = this.PopulateSummaryInformation(summaryInfo, tuples, patchIdTuple, section.Codepage);
117 +
118 + // enumerate transforms
119 + var productCodes = new SortedSet<string>();
120 + var transformNames = new List<string>();
121 + var validTransform = new List<Tuple<string, WindowsInstallerData>>();
122 +
123 + var baselineTuplesById = tuples.OfType<WixPatchBaselineTuple>().ToDictionary(t => t.Id.Id);
124 +
125 + foreach (var mainTransform in this.Transforms)
126 + {
127 + var baselineTuple = baselineTuplesById[mainTransform.Baseline];
128 +
129 + var patchRefTuples = tuples.OfType<WixPatchRefTuple>().ToList();
130 + if (patchRefTuples.Count > 0)
131 + {
132 + if (!this.ReduceTransform(mainTransform.Transform, patchRefTuples))
133 + {
134 + // transform has none of the content authored into this patch
135 + continue;
136 + }
137 + }
138 +
139 + // Validate the transform doesn't break any patch specific rules.
140 + this.Validate(mainTransform);
141 +
142 + // ensure consistent File.Sequence within each Media
143 + var mediaTuple = patchMediaByDiskId[baselineTuple.DiskId];
144 +
145 + // Ensure that files are sequenced after the last file in any transform.
146 + var transformMediaTable = mainTransform.Transform.Tables["Media"];
147 + if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count)
148 + {
149 + foreach (MediaRow transformMediaRow in transformMediaTable.Rows)
150 + {
151 + if (mediaTuple.LastSequence < transformMediaRow.LastSequence)
152 + {
153 + // The Binder will pre-increment the sequence.
154 + mediaTuple.LastSequence = transformMediaRow.LastSequence;
155 + }
156 + }
157 + }
158 +
159 + // Use the Media/@DiskId if greater than the last sequence for backward compatibility.
160 + if (mediaTuple.LastSequence < mediaTuple.DiskId)
161 + {
162 + mediaTuple.LastSequence = mediaTuple.DiskId;
163 + }
164 +
165 + // Ignore media table in the transform.
166 + mainTransform.Transform.Tables.Remove("Media");
167 + mainTransform.Transform.Tables.Remove("WixMedia");
168 + mainTransform.Transform.Tables.Remove("MsiDigitalSignature");
169 +
170 + var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchIdTuple, mainTransform.Transform, mediaTuple, baselineTuple, out var productCode);
171 +
172 + productCode = productCode.ToUpperInvariant();
173 + productCodes.Add(productCode);
174 + validTransform.Add(Tuple.Create(productCode, mainTransform.Transform));
175 +
176 + // attach these transforms to the patch object
177 + // TODO: is this an acceptable way to auto-generate transform stream names?
178 + var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture);
179 + subStorages.Add(new SubStorage(transformName, mainTransform.Transform));
180 + subStorages.Add(new SubStorage("#" + transformName, pairedTransform));
181 +
182 + transformNames.Add(":" + transformName);
183 + transformNames.Add(":#" + transformName);
184 + }
185 +
186 + if (validTransform.Count == 0)
187 + {
188 + this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms());
189 + return subStorages;
190 + }
191 +
192 + // Validate that a patch authored as removable is actually removable
193 + if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1")
194 + {
195 + var uninstallable = true;
196 +
197 + foreach (var entry in validTransform)
198 + {
199 + uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2);
200 + }
201 +
202 + if (!uninstallable)
203 + {
204 + this.Messaging.Write(ErrorMessages.PatchNotRemovable());
205 + return subStorages;
206 + }
207 + }
208 +
209 + // Finish filling tables with transform-dependent data.
210 + productCodes = FinalizePatchProductCodes(tuples, productCodes);
211 +
212 + // Semicolon delimited list of the product codes that can accept the patch.
213 + summaryInfo.Add(SumaryInformationType.PatchProductCodes, new SummaryInformationTuple(patchIdTuple.SourceLineNumbers)
214 + {
215 + PropertyId = SumaryInformationType.PatchProductCodes,
216 + Value = String.Join(";", productCodes)
217 + });
218 +
219 + // Semicolon delimited list of transform substorage names in the order they are applied.
220 + summaryInfo.Add(SumaryInformationType.TransformNames, new SummaryInformationTuple(patchIdTuple.SourceLineNumbers)
221 + {
222 + PropertyId = SumaryInformationType.TransformNames,
223 + Value = String.Join(";", transformNames)
224 + });
225 +
226 + // Put the summary information that was extracted back in now that it is updated.
227 + foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId))
228 + {
229 + section.Tuples.Add(readSummaryInfo);
230 + }
231 +
232 + this.SubStorages = subStorages;
233 +
234 + return subStorages;
235 + }
236 +
237 + private Dictionary<SumaryInformationType, SummaryInformationTuple> ExtractPatchSummaryInfo()
238 + {
239 + var result = new Dictionary<SumaryInformationType, SummaryInformationTuple>();
240 +
241 + foreach (var section in this.Intermediate.Sections)
242 + {
243 + for (var i = section.Tuples.Count - 1; i >= 0; i--)
244 + {
245 + if (section.Tuples[i] is SummaryInformationTuple patchSummaryInfo)
246 + {
247 + // Remove all summary information from the tuples and remember those that
248 + // are not calculated or reserved.
249 + section.Tuples.RemoveAt(i);
250 +
251 + if (patchSummaryInfo.PropertyId != SumaryInformationType.PatchProductCodes &&
252 + patchSummaryInfo.PropertyId != SumaryInformationType.PatchCode &&
253 + patchSummaryInfo.PropertyId != SumaryInformationType.PatchInstallerRequirement &&
254 + patchSummaryInfo.PropertyId != SumaryInformationType.Reserved11 &&
255 + patchSummaryInfo.PropertyId != SumaryInformationType.Reserved14 &&
256 + patchSummaryInfo.PropertyId != SumaryInformationType.Reserved16)
257 + {
258 + result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo);
259 + }
260 + }
261 + }
262 + }
263 +
264 + return result;
265 + }
266 +
267 + private Dictionary<string, MsiPatchMetadataTuple> PopulateSummaryInformation(Dictionary<SumaryInformationType, SummaryInformationTuple> summaryInfo, List<IntermediateTuple> tuples, WixPatchIdTuple patchIdTuple, int codepage)
268 + {
269 + // PID_CODEPAGE
270 + if (!summaryInfo.ContainsKey(SumaryInformationType.Codepage))
271 + {
272 + // Set the code page by default to the same code page for the
273 + // string pool in the database.
274 + AddSummaryInformation(SumaryInformationType.Codepage, codepage.ToString(CultureInfo.InvariantCulture), patchIdTuple.SourceLineNumbers);
275 + }
276 +
277 + // GUID patch code for the patch.
278 + AddSummaryInformation(SumaryInformationType.PatchCode, patchIdTuple.Id.Id, patchIdTuple.SourceLineNumbers);
279 +
280 + // Indicates the minimum Windows Installer version that is required to install the patch.
281 + AddSummaryInformation(SumaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchIdTuple.SourceLineNumbers);
282 +
283 + if (!summaryInfo.ContainsKey(SumaryInformationType.Security))
284 + {
285 + AddSummaryInformation(SumaryInformationType.Security, "4", patchIdTuple.SourceLineNumbers); // Read-only enforced;
286 + }
287 +
288 + // Use authored comments or default to display name.
289 + MsiPatchMetadataTuple commentsTuple = null;
290 +
291 + var metadataTuples = tuples.OfType<MsiPatchMetadataTuple>().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property);
292 +
293 + if (!summaryInfo.ContainsKey(SumaryInformationType.Title) &&
294 + metadataTuples.TryGetValue("DisplayName", out var displayName))
295 + {
296 + AddSummaryInformation(SumaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers);
297 +
298 + // Default comments to use display name as-is.
299 + commentsTuple = displayName;
300 + }
301 +
302 + // TODO: This code below seems unnecessary given the codepage is set at the top of this method.
303 + //if (!summaryInfo.ContainsKey(SumaryInformationType.Codepage) &&
304 + // metadataValues.TryGetValue("CodePage", out var codepage))
305 + //{
306 + // AddSummaryInformation(SumaryInformationType.Codepage, codepage);
307 + //}
308 +
309 + if (!summaryInfo.ContainsKey(SumaryInformationType.PatchPackageName) &&
310 + metadataTuples.TryGetValue("Description", out var description))
311 + {
312 + AddSummaryInformation(SumaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers);
313 + }
314 +
315 + if (!summaryInfo.ContainsKey(SumaryInformationType.Author) &&
316 + metadataTuples.TryGetValue("ManufacturerName", out var manufacturer))
317 + {
318 + AddSummaryInformation(SumaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers);
319 + }
320 +
321 + // Special metadata marshalled through the build.
322 + //var wixMetadataValues = tuples.OfType<WixPatchMetadataTuple>().ToDictionary(t => t.Id.Id, t => t.Value);
323 +
324 + //if (wixMetadataValues.TryGetValue("Comments", out var wixComments))
325 + if (metadataTuples.TryGetValue("Comments", out var wixComments))
326 + {
327 + commentsTuple = wixComments;
328 + }
329 +
330 + // Write the package comments to summary info.
331 + if (!summaryInfo.ContainsKey(SumaryInformationType.Comments) &&
332 + commentsTuple != null)
333 + {
334 + AddSummaryInformation(SumaryInformationType.Comments, commentsTuple.Value, commentsTuple.SourceLineNumbers);
335 + }
336 +
337 + return metadataTuples;
338 +
339 + void AddSummaryInformation(SumaryInformationType type, string value, SourceLineNumber sourceLineNumber)
340 + {
341 + summaryInfo.Add(type, new SummaryInformationTuple(sourceLineNumber)
342 + {
343 + PropertyId = type,
344 + Value = value
345 + });
346 + }
347 + }
348 +
349 + /// <summary>
350 + /// Ensure transform is uninstallable.
351 + /// </summary>
352 + /// <param name="productCode">Product code in transform.</param>
353 + /// <param name="transform">Transform generated by torch.</param>
354 + /// <returns>True if the transform is uninstallable</returns>
355 + private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform)
356 + {
357 + var success = true;
358 +
359 + foreach (var tableName in PatchUninstallBreakingTables)
360 + {
361 + if (transform.TryGetTable(tableName, out var table))
362 + {
363 + foreach (var row in table.Rows)
364 + {
365 + if (row.Operation == RowOperation.Add)
366 + {
367 + success = false;
368 +
369 + var primaryKey = row.GetPrimaryKey('/') ?? String.Empty;
370 +
371 + this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey));
372 + }
373 + }
374 + }
375 + }
376 +
377 + return success;
378 + }
379 +
380 + /// <summary>
381 + /// Reduce the transform according to the patch references.
382 + /// </summary>
383 + /// <param name="transform">transform generated by torch.</param>
384 + /// <param name="patchRefTuples">Table contains patch family filter.</param>
385 + /// <returns>true if the transform is not empty</returns>
386 + private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefTuple> patchRefTuples)
387 + {
388 + // identify sections to keep
389 + var oldSections = new Dictionary<string, Row>();
390 + var newSections = new Dictionary<string, Row>();
391 + var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>();
392 + var sequenceList = new List<Table>();
393 + var componentFeatureAddsIndex = new Dictionary<string, List<string>>();
394 + var customActionTable = new Dictionary<string, Row>();
395 + var directoryTableAdds = new Dictionary<string, Row>();
396 + var featureTableAdds = new Dictionary<string, Row>();
397 + var keptComponents = new Dictionary<string, Row>();
398 + var keptDirectories = new Dictionary<string, Row>();
399 + var keptFeatures = new Dictionary<string, Row>();
400 + var keptLockPermissions = new HashSet<string>();
401 + var keptMsiLockPermissionExs = new HashSet<string>();
402 +
403 + var componentCreateFolderIndex = new Dictionary<string, List<string>>();
404 + var directoryLockPermissionsIndex = new Dictionary<string, List<Row>>();
405 + var directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>();
406 +
407 + foreach (var patchRefTuple in patchRefTuples)
408 + {
409 + var tableName = patchRefTuple.Table;
410 + var key = patchRefTuple.PrimaryKeys;
411 +
412 + // Short circuit filtering if all changes should be included.
413 + if ("*" == tableName && "*" == key)
414 + {
415 + RemoveProductCodeFromTransform(transform);
416 + return true;
417 + }
418 +
419 + if (!transform.Tables.TryGetTable(tableName, out var table))
420 + {
421 + // Table not found.
422 + continue;
423 + }
424 +
425 + // Index the table.
426 + if (!tableKeyRows.TryGetValue(tableName, out var keyRows))
427 + {
428 + keyRows = new Dictionary<string, Row>();
429 + tableKeyRows.Add(tableName, keyRows);
430 +
431 + foreach (var newRow in table.Rows)
432 + {
433 + var primaryKey = newRow.GetPrimaryKey();
434 + keyRows.Add(primaryKey, newRow);
435 + }
436 + }
437 +
438 + if (!keyRows.TryGetValue(key, out var row))
439 + {
440 + // Row not found.
441 + continue;
442 + }
443 +
444 + // Differ.sectionDelimiter
445 + var sections = row.SectionId.Split('/');
446 + oldSections[sections[0]] = row;
447 + newSections[sections[1]] = row;
448 + }
449 +
450 + // throw away sections not referenced
451 + var keptRows = 0;
452 + Table directoryTable = null;
453 + Table featureTable = null;
454 + Table lockPermissionsTable = null;
455 + Table msiLockPermissionsTable = null;
456 +
457 + foreach (var table in transform.Tables)
458 + {
459 + if ("_SummaryInformation" == table.Name)
460 + {
461 + continue;
462 + }
463 +
464 + if (table.Name == "AdminExecuteSequence"
465 + || table.Name == "AdminUISequence"
466 + || table.Name == "AdvtExecuteSequence"
467 + || table.Name == "InstallUISequence"
468 + || table.Name == "InstallExecuteSequence")
469 + {
470 + sequenceList.Add(table);
471 + continue;
472 + }
473 +
474 + for (var i = 0; i < table.Rows.Count; i++)
475 + {
476 + var row = table.Rows[i];
477 +
478 + if (table.Name == "CreateFolder")
479 + {
480 + var createFolderComponentId = row.FieldAsString(1);
481 +
482 + if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList))
483 + {
484 + directoryList = new List<string>();
485 + componentCreateFolderIndex.Add(createFolderComponentId, directoryList);
486 + }
487 +
488 + directoryList.Add(row.FieldAsString(0));
489 + }
490 +
491 + if (table.Name == "CustomAction")
492 + {
493 + customActionTable.Add(row.FieldAsString(0), row);
494 + }
495 +
496 + if (table.Name == "Directory")
497 + {
498 + directoryTable = table;
499 + if (RowOperation.Add == row.Operation)
500 + {
501 + directoryTableAdds.Add(row.FieldAsString(0), row);
502 + }
503 + }
504 +
505 + if (table.Name == "Feature")
506 + {
507 + featureTable = table;
508 + if (RowOperation.Add == row.Operation)
509 + {
510 + featureTableAdds.Add(row.FieldAsString(0), row);
511 + }
512 + }
513 +
514 + if (table.Name == "FeatureComponents")
515 + {
516 + if (RowOperation.Add == row.Operation)
517 + {
518 + var featureId = row.FieldAsString(0);
519 + var componentId = row.FieldAsString(1);
520 +
521 + if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList))
522 + {
523 + featureList = new List<string>();
524 + componentFeatureAddsIndex.Add(componentId, featureList);
525 + }
526 +
527 + featureList.Add(featureId);
528 + }
529 + }
530 +
531 + if (table.Name == "LockPermissions")
532 + {
533 + lockPermissionsTable = table;
534 + if ("CreateFolder" == row.FieldAsString(1))
535 + {
536 + var directoryId = row.FieldAsString(0);
537 +
538 + if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList))
539 + {
540 + rowList = new List<Row>();
541 + directoryLockPermissionsIndex.Add(directoryId, rowList);
542 + }
543 +
544 + rowList.Add(row);
545 + }
546 + }
547 +
548 + if (table.Name == "MsiLockPermissionsEx")
549 + {
550 + msiLockPermissionsTable = table;
551 + if ("CreateFolder" == row.FieldAsString(1))
552 + {
553 + var directoryId = row.FieldAsString(0);
554 +
555 + if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList))
556 + {
557 + rowList = new List<Row>();
558 + directoryMsiLockPermissionsExIndex.Add(directoryId, rowList);
559 + }
560 +
561 + rowList.Add(row);
562 + }
563 + }
564 +
565 + if (null == row.SectionId)
566 + {
567 + table.Rows.RemoveAt(i);
568 + i--;
569 + }
570 + else
571 + {
572 + var sections = row.SectionId.Split('/');
573 + // ignore the row without section id.
574 + if (0 == sections[0].Length && 0 == sections[1].Length)
575 + {
576 + table.Rows.RemoveAt(i);
577 + i--;
578 + }
579 + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
580 + {
581 + if ("Component" == table.Name)
582 + {
583 + keptComponents.Add(row.FieldAsString(0), row);
584 + }
585 +
586 + if ("Directory" == table.Name)
587 + {
588 + keptDirectories.Add(row.FieldAsString(0), row);
589 + }
590 +
591 + if ("Feature" == table.Name)
592 + {
593 + keptFeatures.Add(row.FieldAsString(0), row);
594 + }
595 +
596 + keptRows++;
597 + }
598 + else
599 + {
600 + table.Rows.RemoveAt(i);
601 + i--;
602 + }
603 + }
604 + }
605 + }
606 +
607 + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
608 +
609 + if (null != directoryTable)
610 + {
611 + foreach (var componentRow in keptComponents.Values)
612 + {
613 + var componentId = componentRow.FieldAsString(0);
614 +
615 + if (RowOperation.Add == componentRow.Operation)
616 + {
617 + // Make sure each added component has its required directory and feature heirarchy.
618 + var directoryId = componentRow.FieldAsString(2);
619 + while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow))
620 + {
621 + if (!keptDirectories.ContainsKey(directoryId))
622 + {
623 + directoryTable.Rows.Add(directoryRow);
624 + keptDirectories.Add(directoryId, directoryRow);
625 + keptRows++;
626 + }
627 +
628 + directoryId = directoryRow.FieldAsString(1);
629 + }
630 +
631 + if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds))
632 + {
633 + foreach (var featureId in componentFeatureIds)
634 + {
635 + var currentFeatureId = featureId;
636 + while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow))
637 + {
638 + if (!keptFeatures.ContainsKey(currentFeatureId))
639 + {
640 + featureTable.Rows.Add(featureRow);
641 + keptFeatures.Add(currentFeatureId, featureRow);
642 + keptRows++;
643 + }
644 +
645 + currentFeatureId = featureRow.FieldAsString(1);
646 + }
647 + }
648 + }
649 + }
650 +
651 + // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept.
652 + foreach (var keptComponentId in keptComponents.Keys)
653 + {
654 + if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList))
655 + {
656 + foreach (var directoryId in directoryList)
657 + {
658 + if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList))
659 + {
660 + foreach (var lockPermissionsRow in lockPermissionsRowList)
661 + {
662 + var key = lockPermissionsRow.GetPrimaryKey('/');
663 + if (keptLockPermissions.Add(key))
664 + {
665 + lockPermissionsTable.Rows.Add(lockPermissionsRow);
666 + keptRows++;
667 + }
668 + }
669 + }
670 +
671 + if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList))
672 + {
673 + foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList)
674 + {
675 + var key = msiLockPermissionsExRow.GetPrimaryKey('/');
676 + if (keptMsiLockPermissionExs.Add(key))
677 + {
678 + msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow);
679 + keptRows++;
680 + }
681 + }
682 + }
683 + }
684 + }
685 + }
686 + }
687 + }
688 +
689 + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
690 +
691 + // Delete tables that are empty.
692 + var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name);
693 +
694 + foreach (var tableName in tablesToDelete)
695 + {
696 + transform.Tables.Remove(tableName);
697 + }
698 +
699 + return keptRows > 0;
700 + }
701 +
702 + private void Validate(PatchTransform patchTransform)
703 + {
704 + var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it?
705 + var transform = patchTransform.Transform;
706 +
707 + // Changing the ProdocutCode in a patch transform is not recommended.
708 + if (transform.TryGetTable("Property", out var propertyTable))
709 + {
710 + foreach (var row in propertyTable.Rows)
711 + {
712 + // Only interested in modified rows; fast check.
713 + if (RowOperation.Modify == row.Operation &&
714 + "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal))
715 + {
716 + this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended());
717 + }
718 + }
719 + }
720 +
721 + // If there is nothing in the component table we can return early because the remaining checks are component based.
722 + if (!transform.TryGetTable("Component", out var componentTable))
723 + {
724 + return;
725 + }
726 +
727 + // Index Feature table row operations
728 + var featureOps = new Dictionary<string, RowOperation>();
729 + if (transform.TryGetTable("Feature", out var featureTable))
730 + {
731 + foreach (var row in featureTable.Rows)
732 + {
733 + featureOps[row.FieldAsString(0)] = row.Operation;
734 + }
735 + }
736 +
737 + // Index Component table and check for keypath modifications
738 + var componentKeyPath = new Dictionary<string, string>();
739 + var deletedComponent = new Dictionary<string, Row>();
740 + foreach (var row in componentTable.Rows)
741 + {
742 + var id = row.FieldAsString(0);
743 + var keypath = row.FieldAsString(5) ?? String.Empty;
744 +
745 + componentKeyPath.Add(id, keypath);
746 +
747 + if (RowOperation.Delete == row.Operation)
748 + {
749 + deletedComponent.Add(id, row);
750 + }
751 + else if (RowOperation.Modify == row.Operation)
752 + {
753 + if (row.Fields[1].Modified)
754 + {
755 + // Changing the guid of a component is equal to deleting the old one and adding a new one.
756 + deletedComponent.Add(id, row);
757 + }
758 +
759 + // If the keypath is modified its an error
760 + if (row.Fields[5].Modified)
761 + {
762 + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath));
763 + }
764 + }
765 + }
766 +
767 + // Verify changes in the file table
768 + if (transform.TryGetTable("File", out var fileTable))
769 + {
770 + var componentWithChangedKeyPath = new Dictionary<string, string>();
771 + foreach (var row in fileTable.Rows)
772 + {
773 + if (RowOperation.None == row.Operation)
774 + {
775 + continue;
776 + }
777 +
778 + var fileId = row.FieldAsString(0);
779 + var componentId = row.FieldAsString(1);
780 +
781 + // If this file is the keypath of a component
782 + if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal))
783 + {
784 + if (row.Fields[2].Modified)
785 + {
786 + // You can't change the filename of a file that is the keypath of a component.
787 + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath));
788 + }
789 +
790 + if (!componentWithChangedKeyPath.ContainsKey(componentId))
791 + {
792 + componentWithChangedKeyPath.Add(componentId, fileId);
793 + }
794 + }
795 +
796 + if (RowOperation.Delete == row.Operation)
797 + {
798 + // If the file is removed from a component that is not deleted.
799 + if (!deletedComponent.ContainsKey(componentId))
800 + {
801 + var foundRemoveFileEntry = false;
802 + var filename = Common.GetName(row.FieldAsString(2), false, true);
803 +
804 + if (transform.TryGetTable("RemoveFile", out var removeFileTable))
805 + {
806 + foreach (var removeFileRow in removeFileTable.Rows)
807 + {
808 + if (RowOperation.Delete == removeFileRow.Operation)
809 + {
810 + continue;
811 + }
812 +
813 + if (componentId == removeFileRow.FieldAsString(1))
814 + {
815 + // Check if there is a RemoveFile entry for this file
816 + if (null != removeFileRow[2])
817 + {
818 + var removeFileName = Common.GetName(removeFileRow.FieldAsString(2), false, true);
819 +
820 + // Convert the MSI format for a wildcard string to Regex format.
821 + removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\.");
822 +
823 + var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
824 + if (regex.IsMatch(filename))
825 + {
826 + foundRemoveFileEntry = true;
827 + break;
828 + }
829 + }
830 + }
831 + }
832 + }
833 +
834 + if (!foundRemoveFileEntry)
835 + {
836 + this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId));
837 + }
838 + }
839 + }
840 + }
841 + }
842 +
843 + var featureComponentsTable = transform.Tables["FeatureComponents"];
844 +
845 + if (0 < deletedComponent.Count)
846 + {
847 + // Index FeatureComponents table.
848 + var featureComponents = new Dictionary<string, List<string>>();
849 +
850 + if (null != featureComponentsTable)
851 + {
852 + foreach (var row in featureComponentsTable.Rows)
853 + {
854 + var componentId = row.FieldAsString(1);
855 +
856 + if (!featureComponents.TryGetValue(componentId, out var features))
857 + {
858 + features = new List<string>();
859 + featureComponents.Add(componentId, features);
860 + }
861 +
862 + features.Add(row.FieldAsString(0));
863 + }
864 + }
865 +
866 + // Check to make sure if a component was deleted, the feature was too.
867 + foreach (var entry in deletedComponent)
868 + {
869 + if (featureComponents.TryGetValue(entry.Key, out var features))
870 + {
871 + foreach (var featureId in features)
872 + {
873 + if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete)
874 + {
875 + // The feature was not deleted.
876 + this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath));
877 + }
878 + }
879 + }
880 + }
881 + }
882 +
883 + // Warn if new components are added to existing features
884 + if (null != featureComponentsTable)
885 + {
886 + foreach (var row in featureComponentsTable.Rows)
887 + {
888 + if (RowOperation.Add == row.Operation)
889 + {
890 + // Check if the feature is in the Feature table
891 + var feature_ = row.FieldAsString(0);
892 + var component_ = row.FieldAsString(1);
893 +
894 + // Features may not be present if not referenced
895 + if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_])
896 + {
897 + this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath));
898 + }
899 + }
900 + }
901 + }
902 + }
903 +
904 + /// <summary>
905 + /// Remove the ProductCode property from the transform.
906 + /// </summary>
907 + /// <param name="transform">The transform.</param>
908 + /// <remarks>
909 + /// Changing the ProductCode is not supported in a patch.
910 + /// </remarks>
911 + private static void RemoveProductCodeFromTransform(WindowsInstallerData transform)
912 + {
913 + if (transform.Tables.TryGetTable("Property", out var propertyTable))
914 + {
915 + for (var i = 0; i < propertyTable.Rows.Count; ++i)
916 + {
917 + var propertyRow = propertyTable.Rows[i];
918 + var property = (string)propertyRow[0];
919 +
920 + if ("ProductCode" == property)
921 + {
922 + propertyTable.Rows.RemoveAt(i);
923 + break;
924 + }
925 + }
926 + }
927 + }
928 +
929 + /// <summary>
930 + /// Check if the section is in a PatchFamily.
931 + /// </summary>
932 + /// <param name="oldSection">Section id in target wixout</param>
933 + /// <param name="newSection">Section id in upgrade wixout</param>
934 + /// <param name="oldSections">Dictionary contains section id should be kept in the baseline wixout.</param>
935 + /// <param name="newSections">Dictionary contains section id should be kept in the upgrade wixout.</param>
936 + /// <returns>true if section in patch family</returns>
937 + private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections)
938 + {
939 + var result = false;
940 +
941 + if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection)))
942 + {
943 + result = true;
944 + }
945 + else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection)))
946 + {
947 + result = true;
948 + }
949 +
950 + return result;
951 + }
952 +
953 + /// <summary>
954 + /// Reduce the transform sequence tables.
955 + /// </summary>
956 + /// <param name="sequenceList">ArrayList of tables to be reduced</param>
957 + /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param>
958 + /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param>
959 + /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param>
960 + /// <returns>Number of rows left</returns>
961 + private static int ReduceTransformSequenceTable(List<Table> sequenceList, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections, Dictionary<string, Row> customAction)
962 + {
963 + var keptRows = 0;
964 +
965 + foreach (var currentTable in sequenceList)
966 + {
967 + for (var i = 0; i < currentTable.Rows.Count; i++)
968 + {
969 + var row = currentTable.Rows[i];
970 + var actionName = row.Fields[0].Data.ToString();
971 + var sections = row.SectionId.Split('/');
972 + var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0);
973 +
974 + if (row.Operation == RowOperation.None)
975 + {
976 + // ignore the rows without section id.
977 + if (isSectionIdEmpty)
978 + {
979 + currentTable.Rows.RemoveAt(i);
980 + i--;
981 + }
982 + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
983 + {
984 + keptRows++;
985 + }
986 + else
987 + {
988 + currentTable.Rows.RemoveAt(i);
989 + i--;
990 + }
991 + }
992 + else if (row.Operation == RowOperation.Modify)
993 + {
994 + var sequenceChanged = row.Fields[2].Modified;
995 + var conditionChanged = row.Fields[1].Modified;
996 +
997 + if (sequenceChanged && !conditionChanged)
998 + {
999 + keptRows++;
1000 + }
1001 + else if (!sequenceChanged && conditionChanged)
1002 + {
1003 + if (isSectionIdEmpty)
1004 + {
1005 + currentTable.Rows.RemoveAt(i);
1006 + i--;
1007 + }
1008 + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1009 + {
1010 + keptRows++;
1011 + }
1012 + else
1013 + {
1014 + currentTable.Rows.RemoveAt(i);
1015 + i--;
1016 + }
1017 + }
1018 + else if (sequenceChanged && conditionChanged)
1019 + {
1020 + if (isSectionIdEmpty)
1021 + {
1022 + row.Fields[1].Modified = false;
1023 + keptRows++;
1024 + }
1025 + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1026 + {
1027 + keptRows++;
1028 + }
1029 + else
1030 + {
1031 + row.Fields[1].Modified = false;
1032 + keptRows++;
1033 + }
1034 + }
1035 + }
1036 + else if (row.Operation == RowOperation.Delete)
1037 + {
1038 + if (isSectionIdEmpty)
1039 + {
1040 + // it is a stardard action which is added by wix, we should keep this action.
1041 + row.Operation = RowOperation.None;
1042 + keptRows++;
1043 + }
1044 + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1045 + {
1046 + keptRows++;
1047 + }
1048 + else
1049 + {
1050 + if (customAction.ContainsKey(actionName))
1051 + {
1052 + currentTable.Rows.RemoveAt(i);
1053 + i--;
1054 + }
1055 + else
1056 + {
1057 + // it is a stardard action, we should keep this action.
1058 + row.Operation = RowOperation.None;
1059 + keptRows++;
1060 + }
1061 + }
1062 + }
1063 + else if (row.Operation == RowOperation.Add)
1064 + {
1065 + if (isSectionIdEmpty)
1066 + {
1067 + keptRows++;
1068 + }
1069 + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1070 + {
1071 + keptRows++;
1072 + }
1073 + else
1074 + {
1075 + if (customAction.ContainsKey(actionName))
1076 + {
1077 + currentTable.Rows.RemoveAt(i);
1078 + i--;
1079 + }
1080 + else
1081 + {
1082 + keptRows++;
1083 + }
1084 + }
1085 + }
1086 + }
1087 + }
1088 +
1089 + return keptRows;
1090 + }
1091 +
1092 + /// <summary>
1093 + /// Create the #transform for the given main transform.
1094 + /// </summary>
1095 + private WindowsInstallerData BuildPairedTransform(Dictionary<SumaryInformationType, SummaryInformationTuple> summaryInfo, Dictionary<string, MsiPatchMetadataTuple> patchMetadata, WixPatchIdTuple patchIdTuple, WindowsInstallerData mainTransform, MediaTuple mediaTuple, WixPatchBaselineTuple baselineTuple, out string productCode)
1096 + {
1097 + productCode = null;
1098 +
1099 + var pairedTransform = new WindowsInstallerData(null)
1100 + {
1101 + Type = OutputType.Transform,
1102 + Codepage = mainTransform.Codepage
1103 + };
1104 +
1105 + // lookup productVersion property to correct summaryInformation
1106 + var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1);
1107 +
1108 + var mainSummaryTable = mainTransform.Tables["_SummaryInformation"];
1109 + var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0));
1110 +
1111 + var baselineValidationFlags = ((int)baselineTuple.ValidationFlags).ToString(CultureInfo.InvariantCulture);
1112 +
1113 + if (!mainSummaryRows.ContainsKey((int)SumaryInformationType.TransformValidationFlags))
1114 + {
1115 + var mainSummaryRow = mainSummaryTable.CreateRow(baselineTuple.SourceLineNumbers);
1116 + mainSummaryRow[0] = (int)SumaryInformationType.TransformValidationFlags;
1117 + mainSummaryRow[1] = baselineValidationFlags;
1118 + }
1119 +
1120 + // copy summary information from core transform
1121 + var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]);
1122 +
1123 + foreach (var mainSummaryRow in mainSummaryTable.Rows)
1124 + {
1125 + var type = (SumaryInformationType)mainSummaryRow.FieldAsInteger(0);
1126 + var value = mainSummaryRow.FieldAsString(1);
1127 + switch (type)
1128 + {
1129 + case SumaryInformationType.TransformProductCodes:
1130 + var propertyData = value.Split(';');
1131 + var oldProductVersion = propertyData[0].Substring(38);
1132 + var upgradeCode = propertyData[2];
1133 + productCode = propertyData[0].Substring(0, 38);
1134 +
1135 + if (newProductVersion == null)
1136 + {
1137 + newProductVersion = oldProductVersion;
1138 + }
1139 +
1140 + // Force mainTranform to 'old;new;upgrade' and pairedTransform to 'new;new;upgrade'
1141 + mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode);
1142 + value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode);
1143 + break;
1144 + case SumaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML.
1145 + value = baselineValidationFlags;
1146 + mainSummaryRow[1] = value;
1147 + break;
1148 + }
1149 +
1150 + var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers);
1151 + pairedSummaryRow[0] = mainSummaryRow[0];
1152 + pairedSummaryRow[1] = value;
1153 + }
1154 +
1155 + if (productCode == null)
1156 + {
1157 + this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo());
1158 + return null;
1159 + }
1160 +
1161 + // copy File table
1162 + if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count)
1163 + {
1164 +#if TODO_PATCHING
1165 + // We require file source information.
1166 + var mainWixFileTable = mainTransform.Tables["WixFile"];
1167 + if (null == mainWixFileTable)
1168 + {
1169 + this.Messaging.Write(ErrorMessages.AdminImageRequired(productCode));
1170 + return null;
1171 + }
1172 +
1173 + var mainFileRows = new RowDictionary<FileRow>(mainFileTable);
1174 +
1175 + var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition);
1176 + {
1177 + var mainFileRow = mainFileRows[mainWixFileRow.File];
1178 +
1179 + // set File.Sequence to non null to satisfy transform bind
1180 + mainFileRow.Sequence = 1;
1181 +
1182 + // delete's don't need rows in the paired transform
1183 + if (mainFileRow.Operation == RowOperation.Delete)
1184 + {
1185 + continue;
1186 + }
1187 +
1188 + var pairedFileRow = (FileRow)pairedFileTable.CreateRow(null);
1189 + pairedFileRow.Operation = RowOperation.Modify;
1190 + for (var i = 0; i < mainFileRow.Fields.Length; i++)
1191 + {
1192 + pairedFileRow[i] = mainFileRow[i];
1193 + }
1194 +
1195 + // override authored media for patch bind
1196 + mainWixFileRow.DiskId = mediaTuple.DiskId;
1197 +
1198 + // suppress any change to File.Sequence to avoid bloat
1199 + mainFileRow.Fields[7].Modified = false;
1200 +
1201 + // force File row to appear in the transform
1202 + switch (mainFileRow.Operation)
1203 + {
1204 + case RowOperation.Modify:
1205 + case RowOperation.Add:
1206 + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
1207 + pairedFileRow.Fields[6].Modified = true;
1208 + pairedFileRow.Operation = mainFileRow.Operation;
1209 + break;
1210 + default:
1211 + pairedFileRow.Fields[6].Modified = false;
1212 + break;
1213 + }
1214 + }
1215 +#endif
1216 + }
1217 +
1218 + // Add Media row to pairedTransform
1219 + var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]);
1220 + var pairedMediaRow = pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers);
1221 + pairedMediaRow.Operation = RowOperation.Add;
1222 + pairedMediaRow[0] = mediaTuple.DiskId;
1223 + pairedMediaRow[1] = mediaTuple.LastSequence ?? 0;
1224 + pairedMediaRow[2] = mediaTuple.DiskPrompt;
1225 + pairedMediaRow[3] = mediaTuple.Cabinet;
1226 + pairedMediaRow[4] = mediaTuple.VolumeLabel;
1227 + pairedMediaRow[5] = mediaTuple.Source;
1228 +
1229 + // Add PatchPackage for this Media
1230 + var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]);
1231 + pairedPackageTable.Operation = TableOperation.Add;
1232 + var pairedPackageRow = pairedPackageTable.CreateRow(mediaTuple.SourceLineNumbers);
1233 + pairedPackageRow.Operation = RowOperation.Add;
1234 + pairedPackageRow[0] = patchIdTuple.Id.Id;
1235 + pairedPackageRow[1] = mediaTuple.DiskId;
1236 +
1237 + // Add the property to the patch transform's Property table.
1238 + var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]);
1239 + pairedPropertyTable.Operation = TableOperation.Add;
1240 +
1241 + // Add property to both identify client patches and whether those patches are removable or not
1242 + patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalTuple);
1243 +
1244 + var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalTuple?.SourceLineNumbers);
1245 + pairedPropertyRow.Operation = RowOperation.Add;
1246 + pairedPropertyRow[0] = String.Concat(patchIdTuple.ClientPatchId, ".AllowRemoval");
1247 + pairedPropertyRow[1] = allowRemovalTuple?.Value ?? "0";
1248 +
1249 + // Add this patch code GUID to the patch transform to identify
1250 + // which patches are installed, including in multi-patch
1251 + // installations.
1252 + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdTuple.SourceLineNumbers);
1253 + pairedPropertyRow.Operation = RowOperation.Add;
1254 + pairedPropertyRow[0] = String.Concat(patchIdTuple.ClientPatchId, ".PatchCode");
1255 + pairedPropertyRow[1] = patchIdTuple.Id.Id;
1256 +
1257 + // Add PATCHNEWPACKAGECODE to apply to admin layouts.
1258 + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdTuple.SourceLineNumbers);
1259 + pairedPropertyRow.Operation = RowOperation.Add;
1260 + pairedPropertyRow[0] = "PATCHNEWPACKAGECODE";
1261 + pairedPropertyRow[1] = patchIdTuple.Id.Id;
1262 +
1263 + // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts.
1264 + if (summaryInfo.TryGetValue(SumaryInformationType.Subject, out var subjectTuple))
1265 + {
1266 + pairedPropertyRow = pairedPropertyTable.CreateRow(subjectTuple.SourceLineNumbers);
1267 + pairedPropertyRow.Operation = RowOperation.Add;
1268 + pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT";
1269 + pairedPropertyRow[1] = subjectTuple.Value;
1270 + }
1271 +
1272 + if (summaryInfo.TryGetValue(SumaryInformationType.Comments, out var commentsTuple))
1273 + {
1274 + pairedPropertyRow = pairedPropertyTable.CreateRow(commentsTuple.SourceLineNumbers);
1275 + pairedPropertyRow.Operation = RowOperation.Add;
1276 + pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS";
1277 + pairedPropertyRow[1] = commentsTuple.Value;
1278 + }
1279 +
1280 + return pairedTransform;
1281 + }
1282 +
1283 + private static SortedSet<string> FinalizePatchProductCodes(List<IntermediateTuple> tuples, SortedSet<string> productCodes)
1284 + {
1285 + var patchTargetTuples = tuples.OfType<WixPatchTargetTuple>().ToList();
1286 +
1287 + if (patchTargetTuples.Count > 0)
1288 + {
1289 + var targets = new SortedSet<string>();
1290 + var replace = true;
1291 + foreach (var wixPatchTargetRow in patchTargetTuples)
1292 + {
1293 + var target = wixPatchTargetRow.ProductCode.ToUpperInvariant();
1294 + if (target == "*")
1295 + {
1296 + replace = false;
1297 + }
1298 + else
1299 + {
1300 + targets.Add(target);
1301 + }
1302 + }
1303 +
1304 + // Replace the target ProductCodes with the authored list.
1305 + if (replace)
1306 + {
1307 + productCodes = targets;
1308 + }
1309 + else
1310 + {
1311 + // Copy the authored target ProductCodes into the list.
1312 + foreach (var target in targets)
1313 + {
1314 + productCodes.Add(target);
1315 + }
1316 + }
1317 + }
1318 +
1319 + return productCodes;
1320 + }
1321 + }
1322 +}
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+257 -233
@@ -24,7 +24,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
24
25 private bool disposed;
26
27 - public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, Validator validator)
27 + public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, Validator validator):this(context, backendExtension, null, validator)
28 + {
29 + }
30 +
31 + public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> subStorages, Validator validator)
32 {
33 this.ServiceProvider = context.ServiceProvider;
34
@@ -45,6 +49,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
49 this.OutputPath = context.OutputPath;
50 this.OutputPdbPath = context.OutputPdbPath;
51 this.IntermediateFolder = context.IntermediateFolder;
52 + this.SubStorages = subStorages;
53 this.Validator = validator;
54
55 this.BackendExtensions = backendExtension;
@@ -76,6 +81,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
81
82 private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
83
84 + private IEnumerable<SubStorage> SubStorages { get; }
85 +
86 private Intermediate Intermediate { get; }
87
88 private string OutputPath { get; }
@@ -112,18 +119,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
119 // Load standard tables, authored custom tables, and extension custom tables.
120 TableDefinitionCollection tableDefinitions;
121 {
115 - var command = new LoadTableDefinitionsCommand(section);
122 + var command = new LoadTableDefinitionsCommand(section, this.BackendExtensions);
123 command.Execute();
124
125 tableDefinitions = command.TableDefinitions;
119 -
120 - foreach (var backendExtension in this.BackendExtensions)
121 - {
122 - foreach (var tableDefinition in backendExtension.TableDefinitions)
123 - {
124 - tableDefinitions.Add(tableDefinition);
125 - }
126 - }
126 }
127
128 // Process the summary information table before the other tables.
@@ -186,8 +185,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
185
186 // Sequence all the actions.
187 {
189 - var command = new SequenceActionsCommand(section);
190 - command.Messaging = this.Messaging;
188 + var command = new SequenceActionsCommand(this.Messaging, section);
189 command.Execute();
190 }
191
@@ -196,7 +194,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
194 command.Execute();
195 }
196
199 -#if TODO_FINISH_PATCH
197 +#if TODO_PATCHING
198 ////if (OutputType.Patch == this.Output.Type)
199 ////{
200 //// foreach (SubStorage substorage in this.Output.SubStorages)
@@ -223,130 +221,162 @@ namespace WixToolset.Core.WindowsInstaller.Bind
221 return;
222 }
223
226 - this.Messaging.Write(VerboseMessages.UpdatingFileInformation());
224 + // Call extension
225 + var ExtensionSaidSkip = false;
226
228 - // This must occur after all variables and source paths have been resolved.
229 - List<FileFacade> fileFacades;
227 + WindowsInstallerData output;
228 + if (ExtensionSaidSkip)
229 {
231 - var command = new GetFileFacadesCommand(section);
230 + // Time to create the output object, since we're bypassing everything that touches files.
231 + var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions);
232 command.Execute();
233
234 - fileFacades = command.FileFacades;
234 + output = command.Output;
235 }
236 -
237 - // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules).
236 + else
237 {
239 - var command = new ExtractEmbeddedFilesCommand(this.ExpectedEmbeddedFiles);
240 - command.Execute();
241 - }
238 + this.Messaging.Write(VerboseMessages.UpdatingFileInformation());
239
243 - // Gather information about files that do not come from merge modules.
244 - {
245 - var command = new UpdateFileFacadesCommand(this.Messaging, section);
246 - command.FileFacades = fileFacades;
247 - command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule);
248 - command.OverwriteHash = true;
249 - command.TableDefinitions = tableDefinitions;
250 - command.VariableCache = variableCache;
251 - command.Execute();
252 - }
240 + // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules).
241 + {
242 + var command = new ExtractEmbeddedFilesCommand(this.ExpectedEmbeddedFiles);
243 + command.Execute();
244 + }
245
254 - // Now that the variable cache is populated, resolve any delayed fields.
255 - if (this.DelayedFields.Any())
256 - {
257 - var command = new ResolveDelayedFieldsCommand(this.Messaging, this.DelayedFields, variableCache);
258 - command.Execute();
259 - }
246 + // This must occur after all variables and source paths have been resolved.
247 + List<FileFacade> fileFacades;
248 + {
249 + var command = new GetFileFacadesCommand(section);
250 + command.Execute();
251
261 - // Set generated component guids.
262 - {
263 - var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section);
264 - command.Execute();
265 - }
252 + fileFacades = command.FileFacades;
253 + }
254
267 - // Retrieve file information from merge modules.
268 - if (SectionType.Product == section.Type)
269 - {
270 - var wixMergeTuples = section.Tuples.OfType<WixMergeTuple>().ToList();
255 + // Retrieve file information from merge modules.
256 + if (SectionType.Product == section.Type)
257 + {
258 + var wixMergeTuples = section.Tuples.OfType<WixMergeTuple>().ToList();
259 +
260 + if (wixMergeTuples.Any())
261 + {
262 + containsMergeModules = true;
263 +
264 + var command = new ExtractMergeModuleFilesCommand(this.Messaging, section, wixMergeTuples);
265 + command.FileFacades = fileFacades;
266 + command.OutputInstallerVersion = installerVersion;
267 + command.SuppressLayout = this.SuppressLayout;
268 + command.IntermediateFolder = this.IntermediateFolder;
269 + command.Execute();
270
272 - if (wixMergeTuples.Any())
271 + fileFacades.AddRange(command.MergeModulesFileFacades);
272 + }
273 + }
274 + else if (SectionType.Patch == section.Type)
275 {
274 - containsMergeModules = true;
276 + // Merge transform data into the output object.
277 + //IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
278 +
279 + //var command = new CopyTransformDataCommand(this.Messaging, /*output*/this.SubStorages, tableDefinitions, copyOutFileRows: true);
280 + //command.Output = output;
281 + //command.TableDefinitions = this.TableDefinitions;
282 + //command.CopyOutFileRows = true;
283 + var command = new GetFileFacadesFromTransforms(this.Messaging, this.SubStorages, tableDefinitions);
284 + command.Execute();
285 + var filesFromTransforms = command.FileFacades;
286
276 - var command = new ExtractMergeModuleFilesCommand(this.Messaging, section, wixMergeTuples);
287 + fileFacades.AddRange(filesFromTransforms);
288 + }
289 +
290 + // stop processing if an error previously occurred
291 + if (this.Messaging.EncounteredError)
292 + {
293 + return;
294 + }
295 +
296 + // Gather information about files that do not come from merge modules.
297 + {
298 + var command = new UpdateFileFacadesCommand(this.Messaging, section);
299 command.FileFacades = fileFacades;
278 - command.OutputInstallerVersion = installerVersion;
279 - command.SuppressLayout = this.SuppressLayout;
280 - command.IntermediateFolder = this.IntermediateFolder;
300 + command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule);
301 + command.OverwriteHash = true;
302 + command.TableDefinitions = tableDefinitions;
303 + command.VariableCache = variableCache;
304 command.Execute();
282 -
283 - fileFacades.AddRange(command.MergeModulesFileFacades);
305 }
285 - }
286 -#if TODO_FINISH_PATCH
287 - else if (OutputType.Patch == this.Output.Type)
288 - {
289 - // Merge transform data into the output object.
290 - IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
306
292 - fileFacades.AddRange(filesFromTransform);
293 - }
294 -#endif
307 + // Assign files to media.
308 + Dictionary<int, MediaTuple> assignedMediaRows;
309 + Dictionary<MediaTuple, IEnumerable<FileFacade>> filesByCabinetMedia;
310 + IEnumerable<FileFacade> uncompressedFiles;
311 + {
312 + var command = new AssignMediaCommand(section, this.Messaging);
313 + command.FileFacades = fileFacades;
314 + command.FilesCompressed = compressed;
315 + command.Execute();
316
296 - // stop processing if an error previously occurred
297 - if (this.Messaging.EncounteredError)
298 - {
299 - return;
300 - }
317 + assignedMediaRows = command.MediaRows;
318 + filesByCabinetMedia = command.FileFacadesByCabinetMedia;
319 + uncompressedFiles = command.UncompressedFileFacades;
320 + }
321
302 - // Assign files to media.
303 - Dictionary<int, MediaTuple> assignedMediaRows;
304 - Dictionary<MediaTuple, IEnumerable<FileFacade>> filesByCabinetMedia;
305 - IEnumerable<FileFacade> uncompressedFiles;
306 - {
307 - var command = new AssignMediaCommand(section, this.Messaging);
308 - command.FileFacades = fileFacades;
309 - command.FilesCompressed = compressed;
310 - command.Execute();
322 + // stop processing if an error previously occurred
323 + if (this.Messaging.EncounteredError)
324 + {
325 + return;
326 + }
327
312 - assignedMediaRows = command.MediaRows;
313 - filesByCabinetMedia = command.FileFacadesByCabinetMedia;
314 - uncompressedFiles = command.UncompressedFileFacades;
315 - }
328 + // Now that the variable cache is populated, resolve any delayed fields.
329 + if (this.DelayedFields.Any())
330 + {
331 + var command = new ResolveDelayedFieldsCommand(this.Messaging, this.DelayedFields, variableCache);
332 + command.Execute();
333 + }
334
317 - // stop processing if an error previously occurred
318 - if (this.Messaging.EncounteredError)
319 - {
320 - return;
321 - }
335 + // Set generated component guids.
336 + {
337 + var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section);
338 + command.Execute();
339 + }
340
323 - // Time to create the output object. Try to put as much above here as possible, updating the IR is better.
324 - WindowsInstallerData output;
325 - {
326 - var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions);
327 - command.Execute();
341 + // stop processing if an error previously occurred
342 + if (this.Messaging.EncounteredError)
343 + {
344 + return;
345 + }
346
329 - output = command.Output;
330 - }
347 + // Time to create the output object. Try to put as much above here as possible, updating the IR is better.
348 + {
349 + var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions);
350 + command.Execute();
351
332 - // Update file sequence.
333 - {
334 - var command = new UpdateMediaSequencesCommand(output, fileFacades);
335 - command.Execute();
336 - }
352 + output = command.Output;
353 + }
354
338 - // Modularize identifiers.
339 - if (OutputType.Module == output.Type)
340 - {
341 - var command = new ModularizeCommand(output, modularizationGuid, section.Tuples.OfType<WixSuppressModularizationTuple>());
342 - command.Execute();
343 - }
344 - else // we can create instance transforms since Component Guids are set.
345 - {
355 + // Update file sequence.
356 + {
357 + var command = new UpdateMediaSequencesCommand(output, fileFacades);
358 + command.Execute();
359 + }
360 +
361 + // Modularize identifiers.
362 + if (OutputType.Module == output.Type)
363 + {
364 + var command = new ModularizeCommand(output, modularizationGuid, section.Tuples.OfType<WixSuppressModularizationTuple>());
365 + command.Execute();
366 + }
367 + else if (output.Type == OutputType.Patch)
368 + {
369 + foreach (var storage in this.SubStorages)
370 + {
371 + output.SubStorages.Add(storage);
372 + }
373 + }
374 + else // we can create instance transforms since Component Guids are set.
375 + {
376 #if TODO_FIX_INSTANCE_TRANSFORM
347 - this.CreateInstanceTransforms(this.Output);
377 + this.CreateInstanceTransforms(this.Output);
378 #endif
349 - }
379 + }
380
381 #if TODO_FINISH_UPDATE
382 // Extended binder extensions can be called now that fields are resolved.
@@ -384,116 +414,121 @@ namespace WixToolset.Core.WindowsInstaller.Bind
414 }
415 #endif
416
387 - // Stop processing if an error previously occurred.
388 - if (this.Messaging.EncounteredError)
389 - {
390 - return;
391 - }
417 + this.ValidateComponentGuids(output);
418
393 - // Ensure the intermediate folder is created since delta patches will be
394 - // created there.
395 - Directory.CreateDirectory(this.IntermediateFolder);
419 + // Stop processing if an error previously occurred.
420 + if (this.Messaging.EncounteredError)
421 + {
422 + return;
423 + }
424
397 - if (SectionType.Patch == section.Type && this.DeltaBinaryPatch)
398 - {
399 - var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Tuples.OfType<WixPatchIdTuple>().FirstOrDefault());
400 - command.Execute();
401 - }
425 + // Ensure the intermediate folder is created since delta patches will be
426 + // created there.
427 + Directory.CreateDirectory(this.IntermediateFolder);
428
403 - // create cabinet files and process uncompressed files
404 - var layoutDirectory = Path.GetDirectoryName(this.OutputPath);
405 - if (!this.SuppressLayout || OutputType.Module == output.Type)
406 - {
407 - this.Messaging.Write(VerboseMessages.CreatingCabinetFiles());
408 -
409 - var command = new CreateCabinetsCommand(this.ServiceProvider, this.BackendHelper);
410 - command.CabbingThreadCount = this.CabbingThreadCount;
411 - command.CabCachePath = this.CabCachePath;
412 - command.DefaultCompressionLevel = this.DefaultCompressionLevel;
413 - command.Output = output;
414 - command.Messaging = this.Messaging;
415 - command.BackendExtensions = this.BackendExtensions;
416 - command.LayoutDirectory = layoutDirectory;
417 - command.Compressed = compressed;
418 - command.FileRowsByCabinet = filesByCabinetMedia;
419 - command.ResolveMedia = this.ResolveMedia;
420 - command.TableDefinitions = tableDefinitions;
421 - command.TempFilesLocation = this.IntermediateFolder;
422 - command.Execute();
429 + if (SectionType.Patch == section.Type && this.DeltaBinaryPatch)
430 + {
431 + var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Tuples.OfType<WixPatchIdTuple>().FirstOrDefault());
432 + command.Execute();
433 + }
434
424 - fileTransfers.AddRange(command.FileTransfers);
425 - trackedFiles.AddRange(command.TrackedFiles);
426 - }
435 + // create cabinet files and process uncompressed files
436 + var layoutDirectory = Path.GetDirectoryName(this.OutputPath);
437 + if (!this.SuppressLayout || OutputType.Module == output.Type)
438 + {
439 + this.Messaging.Write(VerboseMessages.CreatingCabinetFiles());
440 +
441 + var command = new CreateCabinetsCommand(this.ServiceProvider, this.BackendHelper);
442 + command.CabbingThreadCount = this.CabbingThreadCount;
443 + command.CabCachePath = this.CabCachePath;
444 + command.DefaultCompressionLevel = this.DefaultCompressionLevel;
445 + command.Output = output;
446 + command.Messaging = this.Messaging;
447 + command.BackendExtensions = this.BackendExtensions;
448 + command.LayoutDirectory = layoutDirectory;
449 + command.Compressed = compressed;
450 + command.FileRowsByCabinet = filesByCabinetMedia;
451 + command.ResolveMedia = this.ResolveMedia;
452 + command.TableDefinitions = tableDefinitions;
453 + command.TempFilesLocation = this.IntermediateFolder;
454 + command.Execute();
455
428 -#if TODO_FINISH_PATCH
429 - if (OutputType.Patch == this.Output.Type)
430 - {
431 - // copy output data back into the transforms
432 - this.CopyToTransformData(this.Output);
433 - }
434 -#endif
456 + fileTransfers.AddRange(command.FileTransfers);
457 + trackedFiles.AddRange(command.TrackedFiles);
458 + }
459
436 - this.ValidateComponentGuids(output);
460 +#if DELETE
461 + if (OutputType.Patch == output.Type)
462 + {
463 + // Copy output data back into the transforms.
464 +#if TODO_PATCHING
465 + var command = new CopyTransformDataCommand(this.Messaging, output, tableDefinitions, copyOutFileRows: false);
466 + command.Execute();
467
438 - // stop processing if an error previously occurred
439 - if (this.Messaging.EncounteredError)
440 - {
441 - return;
442 - }
468 + this.CopyToTransformData(this.Output);
469 +#endif
470 + }
471 +#endif
472
444 - // Generate database file.
445 - this.Messaging.Write(VerboseMessages.GeneratingDatabase());
473 + // stop processing if an error previously occurred
474 + if (this.Messaging.EncounteredError)
475 + {
476 + return;
477 + }
478
447 - {
448 - var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final);
449 - trackedFiles.Add(trackMsi);
479 + // Generate database file.
480 + this.Messaging.Write(VerboseMessages.GeneratingDatabase());
481
451 - var temporaryFiles = this.GenerateDatabase(output, tableDefinitions, trackMsi.Path, false, false);
452 - trackedFiles.AddRange(temporaryFiles);
453 - }
482 + {
483 + var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final);
484 + trackedFiles.Add(trackMsi);
485
455 - // Stop processing if an error previously occurred.
456 - if (this.Messaging.EncounteredError)
457 - {
458 - return;
459 - }
486 + var temporaryFiles = this.GenerateDatabase(output, tableDefinitions, trackMsi.Path, false, false);
487 + trackedFiles.AddRange(temporaryFiles);
488 + }
489
461 - // Merge modules.
462 - if (containsMergeModules)
463 - {
464 - this.Messaging.Write(VerboseMessages.MergingModules());
490 + // Stop processing if an error previously occurred.
491 + if (this.Messaging.EncounteredError)
492 + {
493 + return;
494 + }
495
466 - // Add back possibly suppressed sequence tables since all sequence tables must be present
467 - // for the merge process to work. We'll drop the suppressed sequence tables again as
468 - // necessary.
469 - foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
496 + // Merge modules.
497 + if (containsMergeModules)
498 {
471 - var sequenceTableName = sequence.ToString();
472 - var sequenceTable = output.Tables[sequenceTableName];
499 + this.Messaging.Write(VerboseMessages.MergingModules());
500
474 - if (null == sequenceTable)
501 + // Add back possibly suppressed sequence tables since all sequence tables must be present
502 + // for the merge process to work. We'll drop the suppressed sequence tables again as
503 + // necessary.
504 + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
505 {
476 - sequenceTable = output.EnsureTable(tableDefinitions[sequenceTableName]);
477 - }
506 + var sequenceTableName = sequence.ToString();
507 + var sequenceTable = output.Tables[sequenceTableName];
508
479 - if (0 == sequenceTable.Rows.Count)
480 - {
481 - suppressedTableNames.Add(sequenceTableName);
509 + if (null == sequenceTable)
510 + {
511 + sequenceTable = output.EnsureTable(tableDefinitions[sequenceTableName]);
512 + }
513 +
514 + if (0 == sequenceTable.Rows.Count)
515 + {
516 + suppressedTableNames.Add(sequenceTableName);
517 + }
518 }
483 - }
519
485 - var command = new MergeModulesCommand();
486 - command.FileFacades = fileFacades;
487 - command.Output = output;
488 - command.OutputPath = this.OutputPath;
489 - command.SuppressedTableNames = suppressedTableNames;
490 - command.Execute();
491 - }
520 + var command = new MergeModulesCommand();
521 + command.FileFacades = fileFacades;
522 + command.Output = output;
523 + command.OutputPath = this.OutputPath;
524 + command.SuppressedTableNames = suppressedTableNames;
525 + command.Execute();
526 + }
527
493 - if (this.Messaging.EncounteredError)
494 - {
495 - return;
496 - }
528 + if (this.Messaging.EncounteredError)
529 + {
530 + return;
531 + }
532
533 #if TODO_FINISH_VALIDATION
534 // Validate the output if there is an MSI validator.
@@ -519,27 +554,29 @@ namespace WixToolset.Core.WindowsInstaller.Bind
554 }
555 #endif
556
522 - // Process uncompressed files.
523 - if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
524 - {
525 - var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper, this.PathResolver);
526 - command.Compressed = compressed;
527 - command.FileFacades = uncompressedFiles;
528 - command.LayoutDirectory = layoutDirectory;
529 - command.LongNamesInImage = longNames;
530 - command.ResolveMedia = this.ResolveMedia;
531 - command.DatabasePath = this.OutputPath;
532 - command.Execute();
557 + // Process uncompressed files.
558 + if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
559 + {
560 + var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper, this.PathResolver);
561 + command.Compressed = compressed;
562 + command.FileFacades = uncompressedFiles;
563 + command.LayoutDirectory = layoutDirectory;
564 + command.LongNamesInImage = longNames;
565 + command.ResolveMedia = this.ResolveMedia;
566 + command.DatabasePath = this.OutputPath;
567 + command.Execute();
568
534 - fileTransfers.AddRange(command.FileTransfers);
535 - trackedFiles.AddRange(command.TrackedFiles);
569 + fileTransfers.AddRange(command.FileTransfers);
570 + trackedFiles.AddRange(command.TrackedFiles);
571 + }
572 +
573 + // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables).
574 + trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber)));
575 }
576
577 this.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, output);
578
579 this.FileTransfers = fileTransfers;
541 - // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables).
542 - trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.File.Source.Path, TrackedFileType.Input, f.File.SourceLineNumbers)));
580 this.TrackedFiles = trackedFiles;
581 }
582
@@ -566,7 +603,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
603 return wixout;
604 }
605
569 -#if TODO_FINISH_PATCH
606 +#if TODO_PATCHING
607 /// <summary>
608 /// Copy file data between transform substorages and the patch output object
609 /// </summary>
@@ -936,28 +973,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
973 /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
974 private IEnumerable<ITrackedFile> GenerateDatabase(WindowsInstallerData output, TableDefinitionCollection tableDefinitions, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
975 {
939 - var command = new GenerateDatabaseCommand();
940 - command.BackendHelper = this.BackendHelper;
941 - command.Extensions = this.FileSystemExtensions;
942 - command.Output = output;
943 - command.OutputPath = databaseFile;
944 - command.KeepAddedColumns = keepAddedColumns;
945 - command.UseSubDirectory = useSubdirectory;
946 - command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
947 - command.TableDefinitions = tableDefinitions;
948 - command.IntermediateFolder = this.IntermediateFolder;
949 - command.Codepage = this.Codepage;
976 + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemExtensions, output, databaseFile, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns, this.SuppressAddingValidationRows, useSubdirectory);
977 command.Execute();
978
979 return command.GeneratedTemporaryFiles;
980 }
981
955 - #region IDisposable Support
982 +#region IDisposable Support
983
957 - public void Dispose()
958 - {
959 - this.Dispose(true);
960 - }
984 + public void Dispose() => this.Dispose(true);
985
986 protected virtual void Dispose(bool disposing)
987 {
@@ -972,6 +996,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
996 }
997 }
998
975 - #endregion
999 +#endregion
1000 }
1001 }
src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+95 -102
@@ -15,24 +15,37 @@ namespace WixToolset.Core.WindowsInstaller.Bind
15
16 internal class BindTransformCommand
17 {
18 - public IEnumerable<IFileSystemExtension> Extensions { private get; set; }
18 + public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IFileSystemExtension> extensions, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions)
19 + {
20 + this.Messaging = messaging;
21 + this.BackendHelper = backendHelper;
22 + this.Extensions = extensions;
23 + this.IntermediateFolder = intermediateFolder;
24 + this.Transform = transform;
25 + this.OutputPath = outputPath;
26 + this.TableDefinitions = tableDefinitions;
27 + }
28 +
29 + private IMessaging Messaging { get; }
30
20 - public TableDefinitionCollection TableDefinitions { private get; set; }
31 + private IBackendHelper BackendHelper { get; }
32
22 - public string TempFilesLocation { private get; set; }
33 + private IEnumerable<IFileSystemExtension> Extensions { get; }
34
24 - public WindowsInstallerData Transform { private get; set; }
35 + private TableDefinitionCollection TableDefinitions { get; }
36
26 - public IMessaging Messaging { private get; set; }
37 + private string IntermediateFolder { get; }
38
28 - public string OutputPath { private get; set; }
39 + private WindowsInstallerData Transform { get; }
40 +
41 + private string OutputPath { get; }
42
43 public void Execute()
44 {
32 - int transformFlags = 0;
45 + var transformFlags = 0;
46
34 - WindowsInstallerData targetOutput = new WindowsInstallerData(null);
35 - WindowsInstallerData updatedOutput = new WindowsInstallerData(null);
47 + var targetOutput = new WindowsInstallerData(null);
48 + var updatedOutput = new WindowsInstallerData(null);
49
50 // TODO: handle added columns
51
@@ -49,8 +62,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
62 string targetUpgradeCode = null;
63 string updatedUpgradeCode = null;
64
52 - Table propertyTable = this.Transform.Tables["Property"];
53 - if (null != propertyTable)
65 + if (this.Transform.TryGetTable("Property", out var propertyTable))
66 {
67 for (int i = propertyTable.Rows.Count - 1; i >= 0; i--)
68 {
@@ -68,18 +80,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
80 }
81 }
82
71 - Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
72 - Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
73 - Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]);
74 - Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]);
83 + var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
84 + var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
85 + var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]);
86 + var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]);
87
88 // process special summary information values
77 - foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows)
89 + foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows)
90 {
79 - if ((int)SummaryInformation.Transform.CodePage == (int)row[0])
91 + var summaryId = row.FieldAsInteger(0);
92 + var summaryData = row.FieldAsString(1);
93 +
94 + if ((int)SummaryInformation.Transform.CodePage == summaryId)
95 {
96 // convert from a web name if provided
82 - string codePage = (string)row.Fields[1].Data;
97 + var codePage = summaryData;
98 if (null == codePage)
99 {
100 codePage = "0";
@@ -89,7 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
104 codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture);
105 }
106
92 - string previousCodePage = (string)row.Fields[1].PreviousData;
107 + var previousCodePage = row.Fields[1].PreviousData;
108 if (null == previousCodePage)
109 {
110 previousCodePage = "0";
@@ -99,50 +114,50 @@ namespace WixToolset.Core.WindowsInstaller.Bind
114 previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture);
115 }
116
102 - Row targetCodePageRow = targetSummaryInfo.CreateRow(null);
117 + var targetCodePageRow = targetSummaryInfo.CreateRow(null);
118 targetCodePageRow[0] = 1; // PID_CODEPAGE
119 targetCodePageRow[1] = previousCodePage;
120
106 - Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null);
121 + var updatedCodePageRow = updatedSummaryInfo.CreateRow(null);
122 updatedCodePageRow[0] = 1; // PID_CODEPAGE
123 updatedCodePageRow[1] = codePage;
124 }
110 - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ||
111 - (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
125 + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ||
126 + (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId)
127 {
128 // the target language
114 - string[] propertyData = ((string)row[1]).Split(';');
115 - string lang = 2 == propertyData.Length ? propertyData[1] : "0";
129 + var propertyData = summaryData.Split(';');
130 + var lang = 2 == propertyData.Length ? propertyData[1] : "0";
131
117 - Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo;
118 - Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable;
132 + var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo;
133 + var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable;
134
120 - Row productLanguageRow = tempPropertyTable.CreateRow(null);
135 + var productLanguageRow = tempPropertyTable.CreateRow(null);
136 productLanguageRow[0] = "ProductLanguage";
137 productLanguageRow[1] = lang;
138
139 // set the platform;language on the MSI to be generated
125 - Row templateRow = tempSummaryInfo.CreateRow(null);
140 + var templateRow = tempSummaryInfo.CreateRow(null);
141 templateRow[0] = 7; // PID_TEMPLATE
127 - templateRow[1] = (string)row[1];
142 + templateRow[1] = summaryData;
143 }
129 - else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
144 + else if ((int)SummaryInformation.Transform.ProductCodes == summaryId)
145 {
131 - string[] propertyData = ((string)row[1]).Split(';');
146 + var propertyData = summaryData.Split(';');
147
133 - Row targetProductCodeRow = targetPropertyTable.CreateRow(null);
148 + var targetProductCodeRow = targetPropertyTable.CreateRow(null);
149 targetProductCodeRow[0] = "ProductCode";
150 targetProductCodeRow[1] = propertyData[0].Substring(0, 38);
151
137 - Row targetProductVersionRow = targetPropertyTable.CreateRow(null);
152 + var targetProductVersionRow = targetPropertyTable.CreateRow(null);
153 targetProductVersionRow[0] = "ProductVersion";
154 targetProductVersionRow[1] = propertyData[0].Substring(38);
155
141 - Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null);
156 + var updatedProductCodeRow = updatedPropertyTable.CreateRow(null);
157 updatedProductCodeRow[0] = "ProductCode";
158 updatedProductCodeRow[1] = propertyData[1].Substring(0, 38);
159
145 - Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null);
160 + var updatedProductVersionRow = updatedPropertyTable.CreateRow(null);
161 updatedProductVersionRow[0] = "ProductVersion";
162 updatedProductVersionRow[1] = propertyData[1].Substring(38);
163
@@ -153,7 +168,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
168 targetUpgradeCode = propertyData[2];
169 if (!String.IsNullOrEmpty(targetUpgradeCode))
170 {
156 - Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null);
171 + var targetUpgradeCodeRow = targetPropertyTable.CreateRow(null);
172 targetUpgradeCodeRow[0] = "UpgradeCode";
173 targetUpgradeCodeRow[1] = targetUpgradeCode;
174
@@ -167,16 +182,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
182
183 if (!String.IsNullOrEmpty(updatedUpgradeCode))
184 {
170 - Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null);
185 + var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null);
186 updatedUpgradeCodeRow[0] = "UpgradeCode";
187 updatedUpgradeCodeRow[1] = updatedUpgradeCode;
188 }
189 }
175 - else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
190 + else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId)
191 {
177 - transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
192 + transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture);
193 }
179 - else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0])
194 + else if ((int)SummaryInformation.Transform.Reserved11 == summaryId)
195 {
196 // PID_LASTPRINTED should be null for transforms
197 row.Operation = RowOperation.None;
@@ -184,11 +199,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
199 else
200 {
201 // add everything else as is
187 - Row targetRow = targetSummaryInfo.CreateRow(null);
202 + var targetRow = targetSummaryInfo.CreateRow(null);
203 targetRow[0] = row[0];
204 targetRow[1] = row[1];
205
191 - Row updatedRow = updatedSummaryInfo.CreateRow(null);
206 + var updatedRow = updatedSummaryInfo.CreateRow(null);
207 updatedRow[0] = row[0];
208 updatedRow[1] = row[1];
209 }
@@ -205,7 +220,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
220
221 string emptyFile = null;
222
208 - foreach (Table table in this.Transform.Tables)
223 + foreach (var table in this.Transform.Tables)
224 {
225 // Ignore unreal tables when building transforms except the _Stream table.
226 // These tables are ignored when generating the database so there is no reason
@@ -231,20 +246,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
246 }
247
248 // process row operations
234 - foreach (Row row in table.Rows)
249 + foreach (var row in table.Rows)
250 {
251 switch (row.Operation)
252 {
253 case RowOperation.Add:
239 - Table updatedTable = updatedOutput.EnsureTable(table.Definition);
254 + var updatedTable = updatedOutput.EnsureTable(table.Definition);
255 updatedTable.Rows.Add(row);
256 continue;
257 +
258 case RowOperation.Delete:
243 - Table targetTable = targetOutput.EnsureTable(table.Definition);
259 + var targetTable = targetOutput.EnsureTable(table.Definition);
260 targetTable.Rows.Add(row);
261
262 // fill-in non-primary key values
247 - foreach (Field field in row.Fields)
263 + foreach (var field in row.Fields)
264 {
265 if (!field.Column.PrimaryKey)
266 {
@@ -256,7 +272,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
272 {
273 if (null == emptyFile)
274 {
259 - emptyFile = Path.Combine(this.TempFilesLocation, "empty");
275 + emptyFile = Path.Combine(this.IntermediateFolder, "empty");
276 }
277
278 field.Data = emptyFile;
@@ -273,7 +289,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
289 // Assure that the file table's sequence is populated
290 if ("File" == table.Name)
291 {
276 - foreach (Row fileRow in table.Rows)
292 + foreach (var fileRow in table.Rows)
293 {
294 if (null == fileRow[7])
295 {
@@ -290,60 +306,48 @@ namespace WixToolset.Core.WindowsInstaller.Bind
306 }
307
308 // process modified and unmodified rows
293 - bool modifiedRow = false;
294 - Row targetRow = new Row(null, table.Definition);
295 - Row updatedRow = row;
296 - for (int i = 0; i < row.Fields.Length; i++)
309 + var modifiedRow = false;
310 + var targetRow = new Row(null, table.Definition);
311 + var updatedRow = row;
312 + for (var i = 0; i < row.Fields.Length; i++)
313 {
298 - Field updatedField = row.Fields[i];
314 + var updatedField = row.Fields[i];
315
316 if (updatedField.Modified)
317 {
318 // set a different value in the target row to ensure this value will be modified during transform generation
319 if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable)
320 {
305 - if (null == updatedField.Data || 1 != (int)updatedField.Data)
306 - {
307 - targetRow[i] = 1;
308 - }
309 - else
310 - {
311 - targetRow[i] = 2;
312 - }
321 + var data = updatedField.AsNullableInteger();
322 + targetRow[i] = (data == 1) ? 2 : 1;
323 }
324 else if (ColumnType.Object == updatedField.Column.Type)
325 {
326 if (null == emptyFile)
327 {
318 - emptyFile = Path.Combine(this.TempFilesLocation, "empty");
328 + emptyFile = Path.Combine(this.IntermediateFolder, "empty");
329 }
330
331 targetRow[i] = emptyFile;
332 }
333 else
334 {
325 - if ("0" != (string)updatedField.Data)
326 - {
327 - targetRow[i] = "0";
328 - }
329 - else
330 - {
331 - targetRow[i] = "1";
332 - }
335 + var data = updatedField.AsString();
336 + targetRow[i] = (data == "0") ? "1" : "0";
337 }
338
339 modifiedRow = true;
340 }
341 else if (ColumnType.Object == updatedField.Column.Type)
342 {
339 - ObjectField objectField = (ObjectField)updatedField;
343 + var objectField = (ObjectField)updatedField;
344
345 // create an empty file for comparing against
346 if (null == objectField.PreviousData)
347 {
348 if (null == emptyFile)
349 {
346 - emptyFile = Path.Combine(this.TempFilesLocation, "empty");
350 + emptyFile = Path.Combine(this.IntermediateFolder, "empty");
351 }
352
353 targetRow[i] = emptyFile;
@@ -372,10 +376,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
376 "ProductVersion" == (string)row[0] ||
377 "UpgradeCode" == (string)row[0])))
378 {
375 - Table targetTable = targetOutput.EnsureTable(table.Definition);
379 + var targetTable = targetOutput.EnsureTable(table.Definition);
380 targetTable.Rows.Add(targetRow);
381
378 - Table updatedTable = updatedOutput.EnsureTable(table.Definition);
382 + var updatedTable = updatedOutput.EnsureTable(table.Definition);
383 updatedTable.Rows.Add(updatedRow);
384 }
385 }
@@ -392,38 +396,36 @@ namespace WixToolset.Core.WindowsInstaller.Bind
396 return;
397 }
398
395 - string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath);
396 - string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi"));
397 - string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi"));
399 + var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath);
400 + var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi"));
401 + var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi"));
402
403 try
404 {
405 if (!String.IsNullOrEmpty(emptyFile))
406 {
403 - using (FileStream fileStream = File.Create(emptyFile))
407 + using (var fileStream = File.Create(emptyFile))
408 {
409 }
410 }
411
408 - this.GenerateDatabase(targetOutput, targetDatabaseFile, false);
409 - this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true);
412 + this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false);
413 + this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true);
414
415 // make sure the directory exists
416 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
417
418 // create the transform file
415 - using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly))
419 + using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly))
420 + using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly))
421 {
417 - using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly))
422 + if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath))
423 {
419 - if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath))
420 - {
421 - updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF));
422 - }
423 - else
424 - {
425 - this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers));
426 - }
424 + updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF));
425 + }
426 + else
427 + {
428 + this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers));
429 }
430 }
431 }
@@ -458,16 +460,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
460
461 private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns)
462 {
461 - var command = new GenerateDatabaseCommand();
462 - command.Codepage = output.Codepage;
463 - command.Extensions = this.Extensions;
464 - command.KeepAddedColumns = keepAddedColumns;
465 - command.Output = output;
466 - command.OutputPath = outputPath;
467 - command.TableDefinitions = this.TableDefinitions;
468 - command.IntermediateFolder = this.TempFilesLocation;
469 - command.SuppressAddingValidationRows = true;
470 - command.UseSubDirectory = true;
463 + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.Extensions, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true );
464 command.Execute();
465 }
466 }
src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
+3 -3
@@ -152,7 +152,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
152
153 foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row
154 {
155 - if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes)
155 + if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes)
156 {
157 // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting
158 maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting;
@@ -166,8 +166,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
166
167 var files = cabinetWorkItem.FileFacades
168 .Select(facade => facade.Hash == null ?
169 - new CabinetCompressFile(facade.File.Source.Path, facade.File.Id.Id) :
170 - new CabinetCompressFile(facade.File.Source.Path, facade.File.Id.Id, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4))
169 + new CabinetCompressFile(facade.SourcePath, facade.Id) :
170 + new CabinetCompressFile(facade.SourcePath, facade.Id, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4))
171 .ToList();
172
173 var cab = new Cabinet(cabinetPath);
src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs
+2 -2
@@ -112,8 +112,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
112 private IBindFileWithPath CreateBindFileWithPath(FileFacade facade)
113 {
114 var result = this.ServiceProvider.GetService<IBindFileWithPath>();
115 - result.Id = facade.File.Id.Id;
116 - result.Path = facade.File.Source.Path;
115 + result.Id = facade.Id;
116 + result.Path = facade.SourcePath;
117
118 return result;
119 }
src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
+118 -104
@@ -1,12 +1,15 @@
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 +#if DELETE
4 +
5 namespace WixToolset.Core.WindowsInstaller.Bind
6 {
7 using System;
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 + using System.IO;
11 + using System.Linq;
12 using WixToolset.Core.Bind;
9 - using WixToolset.Core.Native;
13 using WixToolset.Data;
14 using WixToolset.Data.Tuples;
15 using WixToolset.Data.WindowsInstaller;
@@ -16,15 +19,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind
19
20 internal class CopyTransformDataCommand
21 {
19 - public bool CopyOutFileRows { private get; set; }
22 + public CopyTransformDataCommand(IMessaging messaging, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, bool copyOutFileRows)
23 + {
24 + this.Messaging = messaging;
25 + this.Output = output;
26 + this.TableDefinitions = tableDefinitions;
27 + this.CopyOutFileRows = copyOutFileRows;
28 + }
29
21 - public IEnumerable<IFileSystemExtension> Extensions { private get; set; }
30 + private bool CopyOutFileRows { get; }
31
23 - public IMessaging Messaging { private get; set; }
32 + public IEnumerable<IFileSystemExtension> Extensions { get; }
33
25 - public WindowsInstallerData Output { private get; set; }
34 + private IMessaging Messaging { get; }
35
27 - public TableDefinitionCollection TableDefinitions { private get; set; }
36 + private WindowsInstallerData Output { get; }
37 +
38 + private TableDefinitionCollection TableDefinitions { get; }
39
40 public IEnumerable<FileFacade> FileFacades { get; private set; }
41
@@ -32,18 +43,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
43 {
44 Debug.Assert(OutputType.Patch != this.Output.Type);
45
35 - List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null;
46 + var allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null;
47
37 -#if REVISIT_FOR_PATCHING // TODO: Fix this patching related code to work correctly with FileFacades.
38 - bool copyToPatch = (allFileRows != null);
39 - bool copyFromPatch = !copyToPatch;
48 + var copyToPatch = (allFileRows != null);
49 + var copyFromPatch = !copyToPatch;
50
41 - RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>();
51 + var patchMediaRows = new RowDictionary<MediaRow>();
52
43 - Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
53 + var patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
54
45 - Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
46 - Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
55 + var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
56 + var patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
57
58 if (copyFromPatch)
59 {
@@ -51,8 +61,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
61 foreach (WixFileRow patchFileRow in patchFileTable.Rows)
62 {
63 int diskId = patchFileRow.DiskId;
54 - RowDictionary<WixFileRow> mediaFileRows;
55 - if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
64 + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
65 {
66 mediaFileRows = new RowDictionary<WixFileRow>();
67 patchMediaFileRows.Add(diskId, mediaFileRows);
@@ -61,24 +70,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
70 mediaFileRows.Add(patchFileRow);
71 }
72
64 - Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
73 + var patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
74 patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable);
75 }
76
68 - // index paired transforms
69 - Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>();
70 - foreach (SubStorage substorage in this.Output.SubStorages)
71 - {
72 - if (substorage.Name.StartsWith("#"))
73 - {
74 - pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data);
75 - }
76 - }
77 + // Index paired transforms by name without the "#" prefix.
78 + var pairedTransforms = this.Output.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name.Substring(1), s => s.Data);
79 + //Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>();
80 + //foreach (SubStorage substorage in this.Output.SubStorages)
81 + //{
82 + // if (substorage.Name.StartsWith("#"))
83 + // {
84 + // pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data);
85 + // }
86 + //}
87
88 try
89 {
80 - // copy File bind data into substorages
81 - foreach (SubStorage substorage in this.Output.SubStorages)
90 + // Copy File bind data into substorages
91 + foreach (var substorage in this.Output.SubStorages)
92 {
93 if (substorage.Name.StartsWith("#"))
94 {
@@ -86,25 +96,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
96 continue;
97 }
98
89 - Output mainTransform = substorage.Data;
90 - Table mainWixFileTable = mainTransform.Tables["WixFile"];
91 - Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"];
99 + var mainTransform = substorage.Data;
100 + var mainWixFileTable = mainTransform.Tables["WixFile"];
101 + var mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"];
102
103 this.FileManagerCore.ActiveSubStorage = substorage;
104
95 - RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable);
96 - RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>();
105 + var mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable);
106 + var mainMsiFileHashIndex = new RowDictionary<Row>();
107
98 - Table mainFileTable = mainTransform.Tables["File"];
99 - Output pairedTransform = (Output)pairedTransforms[substorage.Name];
108 + var mainFileTable = mainTransform.Tables["File"];
109 + var pairedTransform = pairedTransforms[substorage.Name];
110
111 // copy Media.LastSequence and index the MsiFileHash table if it exists.
112 if (copyFromPatch)
113 {
104 - Table pairedMediaTable = pairedTransform.Tables["Media"];
114 + var pairedMediaTable = pairedTransform.Tables["Media"];
115 foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
116 {
107 - MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
117 + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
118 pairedMediaRow.Fields[1] = patchMediaRow.Fields[1];
119 }
120
@@ -118,8 +128,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
128 }
129
130 // Index File table of pairedTransform
121 - Table pairedFileTable = pairedTransform.Tables["File"];
122 - RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable);
131 + var pairedFileTable = pairedTransform.Tables["File"];
132 + var pairedFileRows = new RowDictionary<FileRow>(pairedFileTable);
133
134 if (null != mainFileTable)
135 {
@@ -140,12 +150,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
150 continue;
151 }
152
143 - WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
153 + var mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
154
155 if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
156 {
147 - ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6];
148 - FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File);
157 + var objectField = (ObjectField)mainWixFileRow.Fields[6];
158 + var pairedFileRow = pairedFileRows.Get(mainFileRow.File);
159
160 // If the file is new, we always need to add it to the patch.
161 if (mainFileRow.Operation != RowOperation.Add)
@@ -169,8 +179,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
179 if (null != pairedFileRow)
180 {
181 // Always patch-added, but never non-compressed.
172 - pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
173 - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
182 + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
183 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
184 pairedFileRow.Fields[6].Modified = true;
185 pairedFileRow.Operation = RowOperation.Modify;
186 }
@@ -179,14 +189,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
189 {
190 // The File is same. We need mark all the attributes as unchanged.
191 mainFileRow.Operation = RowOperation.None;
182 - foreach (Field field in mainFileRow.Fields)
192 + foreach (var field in mainFileRow.Fields)
193 {
194 field.Modified = false;
195 }
196
197 if (null != pairedFileRow)
198 {
189 - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded;
199 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
200 pairedFileRow.Fields[6].Modified = false;
201 pairedFileRow.Operation = RowOperation.None;
202 }
@@ -197,8 +207,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
207 else if (null != pairedFileRow) // RowOperation.Add
208 {
209 // Always patch-added, but never non-compressed.
200 - pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
201 - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
210 + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
211 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
212 pairedFileRow.Fields[6].Modified = true;
213 pairedFileRow.Operation = RowOperation.Add;
214 }
@@ -207,20 +217,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
217 // index patch files by diskId+fileId
218 int diskId = mainWixFileRow.DiskId;
219
210 - RowDictionary<WixFileRow> mediaFileRows;
211 - if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
220 + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
221 {
222 mediaFileRows = new RowDictionary<WixFileRow>();
223 patchMediaFileRows.Add(diskId, mediaFileRows);
224 }
225
217 - string fileId = mainFileRow.File;
218 - WixFileRow patchFileRow = mediaFileRows.Get(fileId);
226 + var fileId = mainFileRow.File;
227 + var patchFileRow = mediaFileRows.Get(fileId);
228 if (copyToPatch)
229 {
230 if (null == patchFileRow)
231 {
223 - FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
232 + var patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
233 patchActualFileRow.CopyFrom(mainFileRow);
234
235 patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
@@ -237,7 +246,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
246 // make sure Source is same. Otherwise we are silently ignoring a file.
247 if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase))
248 {
240 - Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
249 + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
250 }
251
252 // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
@@ -249,11 +258,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
258 // copy data from the patch back to the transform
259 if (null != patchFileRow)
260 {
252 - FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId);
253 - for (int i = 0; i < patchFileRow.Fields.Length; i++)
261 + var pairedFileRow = pairedFileRows.Get(fileId);
262 + for (var i = 0; i < patchFileRow.Fields.Length; i++)
263 {
255 - string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString();
256 - string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString();
264 + var patchValue = patchFileRow[i] == null ? String.Empty : patchFileRow.FieldAsString(i);
265 + var mainValue = mainFileRow[i] == null ? String.Empty : mainFileRow.FieldAsString(i);
266
267 if (1 == i)
268 {
@@ -298,17 +307,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
307 }
308
309 // copy MsiFileHash row for this File
301 - Row patchHashRow;
302 - if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow))
310 + if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow))
311 {
312 patchHashRow = patchFileRow.Hash;
313 }
314
315 if (null != patchHashRow)
316 {
309 - Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
310 - Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
311 - for (int i = 0; i < patchHashRow.Fields.Length; i++)
317 + var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
318 + var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
319 + for (var i = 0; i < patchHashRow.Fields.Length; i++)
320 {
321 mainHashRow[i] = patchHashRow[i];
322 if (i > 1)
@@ -326,12 +334,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
334 List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
335 if (null != patchAssemblyNameRows)
336 {
329 - Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
330 - foreach (Row patchAssemblyNameRow in patchAssemblyNameRows)
337 + var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
338 + foreach (var patchAssemblyNameRow in patchAssemblyNameRows)
339 {
340 // Copy if there isn't an identical modified/added row already in the transform.
333 - bool foundMatchingModifiedRow = false;
334 - foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows)
341 + var foundMatchingModifiedRow = false;
342 + foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows)
343 {
344 if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
345 {
@@ -342,8 +350,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
350
351 if (!foundMatchingModifiedRow)
352 {
345 - Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
346 - for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
353 + var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
354 + for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
355 {
356 mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
357 }
@@ -359,34 +367,36 @@ namespace WixToolset.Core.WindowsInstaller.Bind
367 if (null != patchFileRow.Patch)
368 {
369 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
362 - AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
363 - AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
370 + this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
371 + this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
372
373 // Add to Patch table
366 - Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
374 + var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
375 if (0 == patchTable.Rows.Count)
376 {
377 patchTable.Operation = TableOperation.Add;
378 }
379
372 - Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
380 + var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
381 patchRow[0] = patchFileRow.File;
382 patchRow[1] = patchFileRow.Sequence;
383
376 - FileInfo patchFile = new FileInfo(patchFileRow.Source);
384 + var patchFile = new FileInfo(patchFileRow.Source);
385 patchRow[2] = (int)patchFile.Length;
386 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
387
380 - string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
381 - if (MsiInterop.MsiMaxStreamNameLength < streamName.Length)
388 + var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
389 + if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length)
390 {
391 streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
384 - Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
392 +
393 + var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
394 if (0 == patchHeadersTable.Rows.Count)
395 {
396 patchHeadersTable.Operation = TableOperation.Add;
397 }
389 - Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
398 +
399 + var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
400 patchHeadersRow[0] = streamName;
401 patchHeadersRow[1] = patchFileRow.Patch;
402 patchRow[5] = streamName;
@@ -420,7 +430,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
430 {
431 this.FileManagerCore.ActiveSubStorage = null;
432 }
423 -#endif
433 +
434 this.FileFacades = allFileRows;
435 }
436
@@ -509,17 +519,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
519 foreach (var row in sequenceTable.Rows)
520 {
521 var actionName = row.FieldAsString(0);
512 - if (String.Equals("PatchFiles", actionName, StringComparison.Ordinal))
513 - {
514 - hasPatchFilesAction = true;
515 - }
516 - else if (String.Equals("InstallFiles", actionName, StringComparison.Ordinal))
522 + switch (actionName)
523 {
518 - installFilesSequence = row.FieldAsInteger(2);
519 - }
520 - else if (String.Equals("DuplicateFiles", actionName, StringComparison.Ordinal))
521 - {
522 - duplicateFilesSequence = row.FieldAsInteger(2);
524 + case "PatchFiles":
525 + hasPatchFilesAction = true;
526 + break;
527 +
528 + case "InstallFiles":
529 + installFilesSequence = row.FieldAsInteger(2);
530 + break;
531 +
532 + case "DuplicateFiles":
533 + duplicateFilesSequence = row.FieldAsInteger(2);
534 + break;
535 }
536 }
537 }
@@ -531,8 +543,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
543 /// <param name="output">The output to validate.</param>
544 private void ValidateFileRowChanges(WindowsInstallerData transform)
545 {
534 - Table componentTable = transform.Tables["Component"];
535 - Table fileTable = transform.Tables["File"];
546 + var componentTable = transform.Tables["Component"];
547 + var fileTable = transform.Tables["File"];
548
549 // There's no sense validating keypaths if the transform has no component or file table
550 if (componentTable == null || fileTable == null)
@@ -540,31 +552,31 @@ namespace WixToolset.Core.WindowsInstaller.Bind
552 return;
553 }
554
543 - Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
555 + var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
556
557 // Index the Component table for non-directory & non-registry key paths.
546 - foreach (Row row in componentTable.Rows)
558 + foreach (var row in componentTable.Rows)
559 {
548 - if (null != row.Fields[5].Data &&
549 - 0 != ((int)row.Fields[3].Data & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
560 + var keyPath = row.FieldAsString(5);
561 + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
562 {
551 - componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString());
563 + componentKeyPath.Add(row.FieldAsString(0), keyPath);
564 }
565 }
566
555 - Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>();
556 - Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>();
567 + var componentWithChangedKeyPath = new Dictionary<string, string>();
568 + var componentWithNonKeyPathChanged = new Dictionary<string, string>();
569 // Verify changes in the file table, now that file diffing has occurred
570 foreach (FileRow row in fileTable.Rows)
571 {
560 - string fileId = row.Fields[0].Data.ToString();
561 - string componentId = row.Fields[1].Data.ToString();
562 -
572 if (RowOperation.Modify != row.Operation)
573 {
574 continue;
575 }
576
577 + var fileId = row.FieldAsString(0);
578 + var componentId = row.FieldAsString(1);
579 +
580 // If this file is the keypath of a component
581 if (componentKeyPath.ContainsValue(fileId))
582 {
@@ -582,12 +594,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
594 }
595 }
596
585 - foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged)
597 + foreach (var componentFile in componentWithNonKeyPathChanged)
598 {
599 // Make sure all changes to non keypath files also had a change in the keypath.
588 - if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key))
600 + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath))
601 {
590 - this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key]));
602 + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath));
603 }
604 }
605 }
@@ -614,3 +626,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind
626 }
627 }
628 }
629 +
630 +#endif
src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs
+1 -1
@@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
33 var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false;
34 var apiPatchingSymbolFlags = (PatchSymbolFlagsType)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0);
35
36 -#if REVISIT_FOR_PATCHING
36 +#if TODO_PATCHING
37 foreach (FileFacade facade in this.FileFacades)
38 {
39 if (RowOperation.Modify == facade.File.Operation &&
src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs
+13 -3
@@ -1,4 +1,4 @@
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.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.WindowsInstaller.Bind
4 {
@@ -122,13 +122,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
122 tableString.Append(definition.Name);
123 foreach (var column in definition.Columns)
124 {
125 - // conditionally keep columns added in a transform; otherwise,
126 - // break because columns can only be added at the end
125 + // Conditionally keep columns added in a transform; otherwise,
126 + // break because columns can only be added at the end.
127 if (column.Added && !keepAddedColumns)
128 {
129 break;
130 }
131
132 + if (column.Unreal)
133 + {
134 + continue;
135 + }
136 +
137 if (!first)
138 {
139 columnString.Append('\t');
@@ -168,6 +173,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
173 break;
174 }
175
176 + if (field.Column.Unreal)
177 + {
178 + continue;
179 + }
180 +
181 if (first)
182 {
183 first = false;
src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+166 -187
@@ -40,193 +40,193 @@ namespace WixToolset.Core.WindowsInstaller.Bind
40
41 public void Execute()
42 {
43 - var output = new WindowsInstallerData(this.Section.Tuples.First().SourceLineNumbers);
44 - output.Codepage = this.Section.Codepage;
45 - output.Type = SectionTypeToOutputType(this.Section.Type);
46 -
47 - this.AddSectionToOutput(this.Section, output);
43 + this.Output = new WindowsInstallerData(this.Section.Tuples.First().SourceLineNumbers)
44 + {
45 + Codepage = this.Section.Codepage,
46 + Type = SectionTypeToOutputType(this.Section.Type)
47 + };
48
49 - this.Output = output;
49 + this.AddSectionToOutput();
50 }
51
52 - private void AddSectionToOutput(IntermediateSection section, WindowsInstallerData output)
52 + private void AddSectionToOutput()
53 {
54 - foreach (var tuple in section.Tuples)
54 + foreach (var tuple in this.Section.Tuples)
55 {
56 switch (tuple.Definition.Type)
57 {
58 case TupleDefinitionType.AppSearch:
59 - this.AddTupleDefaultly(tuple, output);
60 - output.EnsureTable(this.TableDefinitions["Signature"]);
59 + this.AddTupleDefaultly(tuple);
60 + this.Output.EnsureTable(this.TableDefinitions["Signature"]);
61 break;
62
63 case TupleDefinitionType.Assembly:
64 - this.AddAssemblyTuple((AssemblyTuple)tuple, output);
64 + this.AddAssemblyTuple((AssemblyTuple)tuple);
65 break;
66
67 case TupleDefinitionType.Binary:
68 - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true);
68 + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true);
69 break;
70
71 case TupleDefinitionType.BBControl:
72 - this.AddBBControlTuple((BBControlTuple)tuple, output);
72 + this.AddBBControlTuple((BBControlTuple)tuple);
73 break;
74
75 case TupleDefinitionType.Class:
76 - this.AddClassTuple((ClassTuple)tuple, output);
76 + this.AddClassTuple((ClassTuple)tuple);
77 break;
78
79 case TupleDefinitionType.Control:
80 - this.AddControlTuple((ControlTuple)tuple, output);
80 + this.AddControlTuple((ControlTuple)tuple);
81 break;
82
83 case TupleDefinitionType.Component:
84 - this.AddComponentTuple((ComponentTuple)tuple, output);
84 + this.AddComponentTuple((ComponentTuple)tuple);
85 break;
86
87 case TupleDefinitionType.CustomAction:
88 - this.AddCustomActionTuple((CustomActionTuple)tuple, output);
88 + this.AddCustomActionTuple((CustomActionTuple)tuple);
89 break;
90
91 case TupleDefinitionType.Dialog:
92 - this.AddDialogTuple((DialogTuple)tuple, output);
92 + this.AddDialogTuple((DialogTuple)tuple);
93 break;
94
95 case TupleDefinitionType.Directory:
96 - this.AddDirectoryTuple((DirectoryTuple)tuple, output);
96 + this.AddDirectoryTuple((DirectoryTuple)tuple);
97 break;
98
99 case TupleDefinitionType.Environment:
100 - this.AddEnvironmentTuple((EnvironmentTuple)tuple, output);
100 + this.AddEnvironmentTuple((EnvironmentTuple)tuple);
101 break;
102
103 case TupleDefinitionType.Error:
104 - this.AddErrorTuple((ErrorTuple)tuple, output);
104 + this.AddErrorTuple((ErrorTuple)tuple);
105 break;
106
107 case TupleDefinitionType.Feature:
108 - this.AddFeatureTuple((FeatureTuple)tuple, output);
108 + this.AddFeatureTuple((FeatureTuple)tuple);
109 break;
110
111 case TupleDefinitionType.File:
112 - this.AddFileTuple((FileTuple)tuple, output);
112 + this.AddFileTuple((FileTuple)tuple);
113 break;
114
115 case TupleDefinitionType.Icon:
116 - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true);
116 + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true);
117 break;
118
119 case TupleDefinitionType.IniFile:
120 - this.AddIniFileTuple((IniFileTuple)tuple, output);
120 + this.AddIniFileTuple((IniFileTuple)tuple);
121 break;
122
123 case TupleDefinitionType.Media:
124 - this.AddMediaTuple((MediaTuple)tuple, output);
124 + this.AddMediaTuple((MediaTuple)tuple);
125 break;
126
127 case TupleDefinitionType.ModuleConfiguration:
128 - this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple, output);
128 + this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple);
129 break;
130
131 case TupleDefinitionType.MsiEmbeddedUI:
132 - this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple, output);
132 + this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple);
133 break;
134
135 case TupleDefinitionType.MsiFileHash:
136 - this.AddMsiFileHashTuple((MsiFileHashTuple)tuple, output);
136 + this.AddMsiFileHashTuple((MsiFileHashTuple)tuple);
137 break;
138
139 case TupleDefinitionType.MsiServiceConfig:
140 - this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple, output);
140 + this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple);
141 break;
142
143 case TupleDefinitionType.MsiServiceConfigFailureActions:
144 - this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple, output);
144 + this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple);
145 break;
146
147 case TupleDefinitionType.MsiShortcutProperty:
148 - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true);
148 + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true);
149 break;
150
151 case TupleDefinitionType.MoveFile:
152 - this.AddMoveFileTuple((MoveFileTuple)tuple, output);
152 + this.AddMoveFileTuple((MoveFileTuple)tuple);
153 break;
154
155 case TupleDefinitionType.ProgId:
156 - this.AddTupleDefaultly(tuple, output);
157 - output.EnsureTable(this.TableDefinitions["Extension"]);
156 + this.AddTupleDefaultly(tuple);
157 + this.Output.EnsureTable(this.TableDefinitions["Extension"]);
158 break;
159
160 case TupleDefinitionType.Property:
161 - this.AddPropertyTuple((PropertyTuple)tuple, output);
161 + this.AddPropertyTuple((PropertyTuple)tuple);
162 break;
163
164 case TupleDefinitionType.RemoveFile:
165 - this.AddRemoveFileTuple((RemoveFileTuple)tuple, output);
165 + this.AddRemoveFileTuple((RemoveFileTuple)tuple);
166 break;
167
168 case TupleDefinitionType.Registry:
169 - this.AddRegistryTuple((RegistryTuple)tuple, output);
169 + this.AddRegistryTuple((RegistryTuple)tuple);
170 break;
171
172 case TupleDefinitionType.RegLocator:
173 - this.AddRegLocatorTuple((RegLocatorTuple)tuple, output);
173 + this.AddRegLocatorTuple((RegLocatorTuple)tuple);
174 break;
175
176 case TupleDefinitionType.RemoveRegistry:
177 - this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple, output);
177 + this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple);
178 break;
179
180 case TupleDefinitionType.ReserveCost:
181 - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true);
181 + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true);
182 break;
183
184 case TupleDefinitionType.ServiceControl:
185 - this.AddServiceControlTuple((ServiceControlTuple)tuple, output);
185 + this.AddServiceControlTuple((ServiceControlTuple)tuple);
186 break;
187
188 case TupleDefinitionType.ServiceInstall:
189 - this.AddServiceInstallTuple((ServiceInstallTuple)tuple, output);
189 + this.AddServiceInstallTuple((ServiceInstallTuple)tuple);
190 break;
191
192 case TupleDefinitionType.Shortcut:
193 - this.AddShortcutTuple((ShortcutTuple)tuple, output);
193 + this.AddShortcutTuple((ShortcutTuple)tuple);
194 break;
195
196 case TupleDefinitionType.Signature:
197 - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true);
197 + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true);
198 break;
199
200 case TupleDefinitionType.SummaryInformation:
201 - this.AddTupleDefaultly(tuple, output, tableName: "_SummaryInformation");
201 + this.AddTupleDefaultly(tuple, tableName: "_SummaryInformation");
202 break;
203
204 case TupleDefinitionType.TextStyle:
205 - this.AddTextStyleTuple((TextStyleTuple)tuple, output);
205 + this.AddTextStyleTuple((TextStyleTuple)tuple);
206 break;
207
208 case TupleDefinitionType.Upgrade:
209 - this.AddUpgradeTuple((UpgradeTuple)tuple, output);
209 + this.AddUpgradeTuple((UpgradeTuple)tuple);
210 break;
211
212 case TupleDefinitionType.WixAction:
213 - this.AddWixActionTuple((WixActionTuple)tuple, output);
213 + this.AddWixActionTuple((WixActionTuple)tuple);
214 break;
215
216 case TupleDefinitionType.WixMediaTemplate:
217 - this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple, output);
217 + this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple);
218 break;
219
220 case TupleDefinitionType.MustBeFromAnExtension:
221 - this.AddTupleFromExtension(tuple, output);
221 + this.AddTupleFromExtension(tuple);
222 break;
223
224 case TupleDefinitionType.WixCustomRow:
225 - this.AddWixCustomRowTuple((WixCustomRowTuple)tuple, output);
225 + this.AddWixCustomRowTuple((WixCustomRowTuple)tuple);
226 break;
227
228 case TupleDefinitionType.WixEnsureTable:
229 - this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple, output);
229 + this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple);
230 break;
231
232 // ignored.
@@ -234,25 +234,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
234 case TupleDefinitionType.WixComponentGroup:
235 case TupleDefinitionType.WixDeltaPatchFile:
236 case TupleDefinitionType.WixFeatureGroup:
237 - break;
237 + case TupleDefinitionType.WixPatchBaseline:
238 + break;
239
240 // Already processed.
241 case TupleDefinitionType.WixCustomTable:
242 break;
243
244 default:
244 - this.AddTupleDefaultly(tuple, output);
245 + this.AddTupleDefaultly(tuple);
246 break;
247 }
248 }
249 }
250
250 - private void AddAssemblyTuple(AssemblyTuple tuple, WindowsInstallerData output)
251 + private void AddAssemblyTuple(AssemblyTuple tuple)
252 {
253 var attributes = tuple.Type == AssemblyType.Win32Assembly ? 1 : (int?)null;
254
254 - var table = output.EnsureTable(this.TableDefinitions["MsiAssembly"]);
255 - var row = table.CreateRow(tuple.SourceLineNumbers);
255 + var row = this.CreateRow(tuple, "MsiAssembly");
256 row[0] = tuple.ComponentRef;
257 row[1] = tuple.FeatureRef;
258 row[2] = tuple.ManifestFileRef;
@@ -260,7 +260,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
260 row[4] = attributes;
261 }
262
263 - private void AddBBControlTuple(BBControlTuple tuple, WindowsInstallerData output)
263 + private void AddBBControlTuple(BBControlTuple tuple)
264 {
265 var attributes = tuple.Attributes;
266 attributes |= tuple.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0;
@@ -272,8 +272,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
272 attributes |= tuple.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0;
273 attributes |= tuple.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0;
274
275 - var table = output.EnsureTable(this.TableDefinitions["BBControl"]);
276 - var row = table.CreateRow(tuple.SourceLineNumbers);
275 + var row = this.CreateRow(tuple, "BBControl");
276 row[0] = tuple.BillboardRef;
277 row[1] = tuple.BBControl;
278 row[2] = tuple.Type;
@@ -285,10 +284,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
284 row[8] = tuple.Text;
285 }
286
288 - private void AddClassTuple(ClassTuple tuple, WindowsInstallerData output)
287 + private void AddClassTuple(ClassTuple tuple)
288 {
290 - var table = output.EnsureTable(this.TableDefinitions["Class"]);
291 - var row = table.CreateRow(tuple.SourceLineNumbers);
289 + var row = this.CreateRow(tuple, "Class");
290 row[0] = tuple.CLSID;
291 row[1] = tuple.Context;
292 row[2] = tuple.ComponentRef;
@@ -304,7 +302,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
302 row[12] = tuple.RelativePath ? (int?)1 : null;
303 }
304
307 - private void AddControlTuple(ControlTuple tuple, WindowsInstallerData output)
305 + private void AddControlTuple(ControlTuple tuple)
306 {
307 var text = tuple.Text;
308 var attributes = tuple.Attributes;
@@ -329,8 +327,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
327 text = String.Concat(text, " ");
328 }
329
332 - var table = output.EnsureTable(this.TableDefinitions["Control"]);
333 - var row = table.CreateRow(tuple.SourceLineNumbers);
330 + var row = this.CreateRow(tuple, "Control");
331 row[0] = tuple.DialogRef;
332 row[1] = tuple.Control;
333 row[2] = tuple.Type;
@@ -344,7 +341,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
341 row[10] = tuple.Help;
342 }
343
347 - private void AddComponentTuple(ComponentTuple tuple, WindowsInstallerData output)
344 + private void AddComponentTuple(ComponentTuple tuple)
345 {
346 var attributes = ComponentLocation.Either == tuple.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0;
347 attributes |= ComponentLocation.SourceOnly == tuple.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0;
@@ -359,8 +356,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
356 attributes |= tuple.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0;
357 attributes |= tuple.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0;
358
362 - var table = output.EnsureTable(this.TableDefinitions["Component"]);
363 - var row = table.CreateRow(tuple.SourceLineNumbers);
359 + var row = this.CreateRow(tuple, "Component");
360 row[0] = tuple.Id.Id;
361 row[1] = tuple.ComponentId;
362 row[2] = tuple.DirectoryRef;
@@ -369,7 +365,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
365 row[5] = tuple.KeyPath;
366 }
367
372 - private void AddCustomActionTuple(CustomActionTuple tuple, WindowsInstallerData output)
368 + private void AddCustomActionTuple(CustomActionTuple tuple)
369 {
370 var type = tuple.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0;
371 type |= tuple.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0;
@@ -396,8 +392,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
392 type |= tuple.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0;
393 }
394
399 - var table = output.EnsureTable(this.TableDefinitions["CustomAction"]);
400 - var row = table.CreateRow(tuple.SourceLineNumbers);
395 + var row = this.CreateRow(tuple, "CustomAction");
396 row[0] = tuple.Id.Id;
397 row[1] = type;
398 row[2] = tuple.Source;
@@ -405,7 +400,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
400 row[4] = tuple.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null;
401 }
402
408 - private void AddDialogTuple(DialogTuple tuple, WindowsInstallerData output)
403 + private void AddDialogTuple(DialogTuple tuple)
404 {
405 var attributes = tuple.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0;
406 attributes|= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0;
@@ -419,8 +414,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
414 attributes|= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0;
415 attributes|= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0;
416
422 - var table = output.EnsureTable(this.TableDefinitions["Dialog"]);
423 - var row = table.CreateRow(tuple.SourceLineNumbers);
417 + var row = this.CreateRow(tuple, "Dialog");
418 row[0] = tuple.Id.Id;
419 row[1] = tuple.HCentering;
420 row[2] = tuple.VCentering;
@@ -432,10 +426,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
426 row[8] = tuple.DefaultControlRef;
427 row[9] = tuple.CancelControlRef;
428
435 - output.EnsureTable(this.TableDefinitions["ListBox"]);
429 + this.Output.EnsureTable(this.TableDefinitions["ListBox"]);
430 }
431
438 - private void AddDirectoryTuple(DirectoryTuple tuple, WindowsInstallerData output)
432 + private void AddDirectoryTuple(DirectoryTuple tuple)
433 {
434 var sourceName = GetMsiFilenameValue(tuple.SourceShortName, tuple.SourceName);
435 var targetName = GetMsiFilenameValue(tuple.ShortName, tuple.Name);
@@ -447,14 +441,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
441
442 var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName ;
443
450 - var table = output.EnsureTable(this.TableDefinitions["Directory"]);
451 - var row = table.CreateRow(tuple.SourceLineNumbers);
444 + var row = this.CreateRow(tuple, "Directory");
445 row[0] = tuple.Id.Id;
446 row[1] = tuple.ParentDirectoryRef;
447 row[2] = defaultDir;
448 }
449
457 - private void AddEnvironmentTuple(EnvironmentTuple tuple, WindowsInstallerData output)
450 + private void AddEnvironmentTuple(EnvironmentTuple tuple)
451 {
452 var action = String.Empty;
453 var system = tuple.System ? "*" : String.Empty;
@@ -484,23 +477,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
477 break;
478 }
479
487 - var table = output.EnsureTable(this.TableDefinitions["Environment"]);
488 - var row = table.CreateRow(tuple.SourceLineNumbers);
480 + var row = this.CreateRow(tuple, "Environment");
481 row[0] = tuple.Id.Id;
482 row[1] = String.Concat(action, uninstall, system, tuple.Name);
483 row[2] = value;
484 row[3] = tuple.ComponentRef;
485 }
486
495 - private void AddErrorTuple(ErrorTuple tuple, WindowsInstallerData output)
487 + private void AddErrorTuple(ErrorTuple tuple)
488 {
497 - var table = output.EnsureTable(this.TableDefinitions["Error"]);
498 - var row = table.CreateRow(tuple.SourceLineNumbers);
489 + var row = this.CreateRow(tuple, "Error");
490 row[0] = Convert.ToInt32(tuple.Id.Id);
491 row[1] = tuple.Message;
492 }
493
503 - private void AddFeatureTuple(FeatureTuple tuple, WindowsInstallerData output)
494 + private void AddFeatureTuple(FeatureTuple tuple)
495 {
496 var attributes = tuple.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0;
497 attributes |= tuple.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0;
@@ -508,8 +499,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
499 attributes |= FeatureInstallDefault.Source == tuple.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0;
500 attributes |= FeatureTypicalDefault.Advertise == tuple.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0;
501
511 - var table = output.EnsureTable(this.TableDefinitions["Feature"]);
512 - var row = table.CreateRow(tuple.SourceLineNumbers);
502 + var row = this.CreateRow(tuple, "Feature");
503 row[0] = tuple.Id.Id;
504 row[1] = tuple.ParentFeatureRef;
505 row[2] = tuple.Title;
@@ -520,16 +510,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
510 row[7] = attributes;
511 }
512
523 - private void AddFileTuple(FileTuple tuple, WindowsInstallerData output)
513 + private void AddFileTuple(FileTuple tuple)
514 {
525 - var table = output.EnsureTable(this.TableDefinitions["File"]);
526 - var row = (FileRow)table.CreateRow(tuple.SourceLineNumbers);
515 + var row = (FileRow)this.CreateRow(tuple, "File");
516 row.File = tuple.Id.Id;
517 row.Component = tuple.ComponentRef;
518 row.FileName = GetMsiFilenameValue(tuple.ShortName, tuple.Name);
519 row.FileSize = tuple.FileSize;
520 row.Version = tuple.Version;
521 row.Language = tuple.Language;
522 + row.DiskId = tuple.DiskId ?? 1; // TODO: is 0 the correct thing to default here
523 + row.Source = tuple.Source.Path;
524
525 var attributes = (tuple.Attributes & FileTupleAttributes.Checksum) == FileTupleAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0;
526 attributes |= (tuple.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0;
@@ -542,19 +533,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
533
534 if (!String.IsNullOrEmpty(tuple.FontTitle))
535 {
545 - var fontTable = output.EnsureTable(this.TableDefinitions["Font"]);
546 - var fontRow = fontTable.CreateRow(tuple.SourceLineNumbers);
536 + var fontRow = this.CreateRow(tuple, "Font");
537 fontRow[0] = tuple.Id.Id;
538 fontRow[1] = tuple.FontTitle;
539 }
540 }
541
552 - private void AddIniFileTuple(IniFileTuple tuple, WindowsInstallerData output)
542 + private void AddIniFileTuple(IniFileTuple tuple)
543 {
544 var tableName = (InifFileActionType.AddLine == tuple.Action || InifFileActionType.AddTag == tuple.Action || InifFileActionType.CreateLine == tuple.Action) ? "IniFile" : "RemoveIniFile";
545
556 - var table = output.EnsureTable(this.TableDefinitions[tableName]);
557 - var row = table.CreateRow(tuple.SourceLineNumbers);
546 + var row = this.CreateRow(tuple, tableName);
547 row[0] = tuple.Id.Id;
548 row[1] = tuple.FileName;
549 row[2] = tuple.DirProperty;
@@ -565,12 +554,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
554 row[7] = tuple.ComponentRef;
555 }
556
568 - private void AddMediaTuple(MediaTuple tuple, WindowsInstallerData output)
557 + private void AddMediaTuple(MediaTuple tuple)
558 {
559 if (this.Section.Type != SectionType.Module)
560 {
572 - var table = output.EnsureTable(this.TableDefinitions["Media"]);
573 - var row = (MediaRow)table.CreateRow(tuple.SourceLineNumbers);
561 + var row = (MediaRow)this.CreateRow(tuple, "Media");
562 row.DiskId = tuple.DiskId;
563 row.LastSequence = tuple.LastSequence ?? 0;
564 row.DiskPrompt = tuple.DiskPrompt;
@@ -580,10 +568,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
568 }
569 }
570
583 - private void AddModuleConfigurationTuple(ModuleConfigurationTuple tuple, WindowsInstallerData output)
571 + private void AddModuleConfigurationTuple(ModuleConfigurationTuple tuple)
572 {
585 - var table = output.EnsureTable(this.TableDefinitions["ModuleConfiguration"]);
586 - var row = table.CreateRow(tuple.SourceLineNumbers);
573 + var row = this.CreateRow(tuple, "ModuleConfiguration");
574 row[0] = tuple.Id.Id;
575 row[1] = tuple.Format;
576 row[2] = tuple.Type;
@@ -597,13 +584,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
584 row[9] = tuple.HelpKeyword;
585 }
586
600 - private void AddMsiEmbeddedUITuple(MsiEmbeddedUITuple tuple, WindowsInstallerData output)
587 + private void AddMsiEmbeddedUITuple(MsiEmbeddedUITuple tuple)
588 {
589 var attributes = tuple.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0;
590 attributes |= tuple.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0;
591
605 - var table = output.EnsureTable(this.TableDefinitions["MsiEmbeddedUI"]);
606 - var row = table.CreateRow(tuple.SourceLineNumbers);
592 + var row = this.CreateRow(tuple, "MsiEmbeddedUI");
593 row[0] = tuple.Id.Id;
594 row[1] = tuple.FileName;
595 row[2] = attributes;
@@ -611,10 +597,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
597 row[4] = tuple.Source;
598 }
599
614 - private void AddMsiFileHashTuple(MsiFileHashTuple tuple, WindowsInstallerData output)
600 + private void AddMsiFileHashTuple(MsiFileHashTuple tuple)
601 {
616 - var table = output.EnsureTable(this.TableDefinitions["MsiFileHash"]);
617 - var row = table.CreateRow(tuple.SourceLineNumbers);
602 + var row = this.CreateRow(tuple, "MsiFileHash");
603 row[0] = tuple.Id.Id;
604 row[1] = tuple.Options;
605 row[2] = tuple.HashPart1;
@@ -623,14 +608,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
608 row[5] = tuple.HashPart4;
609 }
610
626 - private void AddMsiServiceConfigTuple(MsiServiceConfigTuple tuple, WindowsInstallerData output)
611 + private void AddMsiServiceConfigTuple(MsiServiceConfigTuple tuple)
612 {
613 var events = tuple.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0;
614 events |= tuple.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0;
615 events |= tuple.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0;
616
632 - var table = output.EnsureTable(this.TableDefinitions["MsiServiceConfigFailureActions"]);
633 - var row = table.CreateRow(tuple.SourceLineNumbers);
617 + var row = this.CreateRow(tuple, "MsiServiceConfigFailureActions");
618 row[0] = tuple.Id.Id;
619 row[1] = tuple.Name;
620 row[2] = events;
@@ -639,14 +623,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
623 row[5] = tuple.ComponentRef;
624 }
625
642 - private void AddMsiServiceConfigFailureActionsTuple(MsiServiceConfigFailureActionsTuple tuple, WindowsInstallerData output)
626 + private void AddMsiServiceConfigFailureActionsTuple(MsiServiceConfigFailureActionsTuple tuple)
627 {
628 var events = tuple.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0;
629 events |= tuple.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0;
630 events |= tuple.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0;
631
648 - var table = output.EnsureTable(this.TableDefinitions["MsiServiceConfig"]);
649 - var row = table.CreateRow(tuple.SourceLineNumbers);
632 + var row = this.CreateRow(tuple, "MsiServiceConfig");
633 row[0] = tuple.Id.Id;
634 row[1] = tuple.Name;
635 row[2] = events;
@@ -658,10 +641,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
641 row[8] = tuple.ComponentRef;
642 }
643
661 - private void AddMoveFileTuple(MoveFileTuple tuple, WindowsInstallerData output)
644 + private void AddMoveFileTuple(MoveFileTuple tuple)
645 {
663 - var table = output.EnsureTable(this.TableDefinitions["MoveFile"]);
664 - var row = table.CreateRow(tuple.SourceLineNumbers);
646 + var row = this.CreateRow(tuple, "MoveFile");
647 row[0] = tuple.Id.Id;
648 row[1] = tuple.ComponentRef;
649 row[2] = tuple.SourceName;
@@ -671,26 +653,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
653 row[6] = tuple.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0;
654 }
655
674 - private void AddPropertyTuple(PropertyTuple tuple, WindowsInstallerData output)
656 + private void AddPropertyTuple(PropertyTuple tuple)
657 {
658 if (String.IsNullOrEmpty(tuple.Value))
659 {
660 return;
661 }
662
681 - var table = output.EnsureTable(this.TableDefinitions["Property"]);
682 - var row = (PropertyRow)table.CreateRow(tuple.SourceLineNumbers);
663 + var row = (PropertyRow)this.CreateRow(tuple, "Property");
664 row.Property = tuple.Id.Id;
665 row.Value = tuple.Value;
666 }
667
687 - private void AddRemoveFileTuple(RemoveFileTuple tuple, WindowsInstallerData output)
668 + private void AddRemoveFileTuple(RemoveFileTuple tuple)
669 {
670 var installMode = tuple.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0;
671 installMode |= tuple.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0;
672
692 - var table = output.EnsureTable(this.TableDefinitions["RemoveFile"]);
693 - var row = table.CreateRow(tuple.SourceLineNumbers);
673 + var row = this.CreateRow(tuple, "RemoveFile");
674 row[0] = tuple.Id.Id;
675 row[1] = tuple.ComponentRef;
676 row[2] = tuple.FileName;
@@ -698,7 +678,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
678 row[4] = installMode;
679 }
680
701 - private void AddRegistryTuple(RegistryTuple tuple, WindowsInstallerData output)
681 + private void AddRegistryTuple(RegistryTuple tuple)
682 {
683 var value = tuple.Value;
684
@@ -740,8 +720,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
720 break;
721 }
722
743 - var table = output.EnsureTable(this.TableDefinitions["Registry"]);
744 - var row = table.CreateRow(tuple.SourceLineNumbers);
723 + var row = this.CreateRow(tuple, "Registry");
724 row[0] = tuple.Id.Id;
725 row[1] = tuple.Root;
726 row[2] = tuple.Key;
@@ -750,13 +729,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
729 row[5] = tuple.ComponentRef;
730 }
731
753 - private void AddRegLocatorTuple(RegLocatorTuple tuple, WindowsInstallerData output)
732 + private void AddRegLocatorTuple(RegLocatorTuple tuple)
733 {
734 var type = (int)tuple.Type;
735 type |= tuple.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0;
736
758 - var table = output.EnsureTable(this.TableDefinitions["RegLocator"]);
759 - var row = table.CreateRow(tuple.SourceLineNumbers);
737 + var row = this.CreateRow(tuple, "RegLocator");
738 row[0] = tuple.Id.Id;
739 row[1] = tuple.Root;
740 row[2] = tuple.Key;
@@ -764,12 +742,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
742 row[4] = type;
743 }
744
767 - private void AddRemoveRegistryTuple(RemoveRegistryTuple tuple, WindowsInstallerData output)
745 + private void AddRemoveRegistryTuple(RemoveRegistryTuple tuple)
746 {
747 if (tuple.Action == RemoveRegistryActionType.RemoveOnInstall)
748 {
771 - var table = output.EnsureTable(this.TableDefinitions["RemoveRegistry"]);
772 - var row = table.CreateRow(tuple.SourceLineNumbers);
749 + var row = this.CreateRow(tuple, "RemoveRegistry");
750 row[0] = tuple.Id.Id;
751 row[1] = tuple.Root;
752 row[2] = tuple.Key;
@@ -778,8 +755,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
755 }
756 else // Registry table is used to remove registry keys on uninstall.
757 {
781 - var table = output.EnsureTable(this.TableDefinitions["Registry"]);
782 - var row = table.CreateRow(tuple.SourceLineNumbers);
758 + var row = this.CreateRow(tuple, "Registry");
759 row[0] = tuple.Id.Id;
760 row[1] = tuple.Root;
761 row[2] = tuple.Key;
@@ -788,7 +764,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
764 }
765 }
766
791 - private void AddServiceControlTuple(ServiceControlTuple tuple, WindowsInstallerData output)
767 + private void AddServiceControlTuple(ServiceControlTuple tuple)
768 {
769 var events = tuple.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0;
770 events |= tuple.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0;
@@ -797,8 +773,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
773 events |= tuple.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0;
774 events |= tuple.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0;
775
800 - var table = output.EnsureTable(this.TableDefinitions["ServiceControl"]);
801 - var row = table.CreateRow(tuple.SourceLineNumbers);
776 + var row = this.CreateRow(tuple, "ServiceControl");
777 row[0] = tuple.Id.Id;
778 row[1] = tuple.Name;
779 row[2] = events;
@@ -810,7 +785,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
785 row[5] = tuple.ComponentRef;
786 }
787
813 - private void AddServiceInstallTuple(ServiceInstallTuple tuple, WindowsInstallerData output)
788 + private void AddServiceInstallTuple(ServiceInstallTuple tuple)
789 {
790 var errorControl = (int)tuple.ErrorControl;
791 errorControl |= tuple.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0;
@@ -818,8 +793,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
793 var serviceType = (int)tuple.ServiceType;
794 serviceType |= tuple.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0;
795
821 - var table = output.EnsureTable(this.TableDefinitions["ServiceInstall"]);
822 - var row = table.CreateRow(tuple.SourceLineNumbers);
796 + var row = this.CreateRow(tuple, "ServiceInstall");
797 row[0] = tuple.Id.Id;
798 row[1] = tuple.Name;
799 row[2] = tuple.DisplayName;
@@ -835,10 +809,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
809 row[12] = tuple.Description;
810 }
811
838 - private void AddShortcutTuple(ShortcutTuple tuple, WindowsInstallerData output)
812 + private void AddShortcutTuple(ShortcutTuple tuple)
813 {
840 - var table = output.EnsureTable(this.TableDefinitions["Shortcut"]);
841 - var row = table.CreateRow(tuple.SourceLineNumbers);
814 + var row = this.CreateRow(tuple, "Shortcut");
815 row[0] = tuple.Id.Id;
816 row[1] = tuple.DirectoryRef;
817 row[2] = GetMsiFilenameValue(tuple.ShortName, tuple.Name);
@@ -857,7 +830,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
830 row[15] = tuple.DescriptionResourceId;
831 }
832
860 - private void AddTextStyleTuple(TextStyleTuple tuple, WindowsInstallerData output)
833 + private void AddTextStyleTuple(TextStyleTuple tuple)
834 {
835 var styleBits = tuple.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0;
836 styleBits |= tuple.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0;
@@ -873,8 +846,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
846 color += (long)(tuple.Blue ?? 0) * 65536;
847 }
848
876 - var table = output.EnsureTable(this.TableDefinitions["TextStyle"]);
877 - var row = table.CreateRow(tuple.SourceLineNumbers);
849 + var row = this.CreateRow(tuple, "TextStyle");
850 row[0] = tuple.Id.Id;
851 row[1] = tuple.FaceName;
852 row[2] = tuple.Size;
@@ -882,10 +854,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
854 row[4] = styleBits == 0 ? null : (int?)styleBits;
855 }
856
885 - private void AddUpgradeTuple(UpgradeTuple tuple, WindowsInstallerData output)
857 + private void AddUpgradeTuple(UpgradeTuple tuple)
858 {
887 - var table = output.EnsureTable(this.TableDefinitions["Upgrade"]);
888 - var row = (UpgradeRow)table.CreateRow(tuple.SourceLineNumbers);
859 + var row = (UpgradeRow)this.CreateRow(tuple, "Upgrade");
860 row.UpgradeCode = tuple.UpgradeCode;
861 row.VersionMin = tuple.VersionMin;
862 row.VersionMax = tuple.VersionMax;
@@ -902,72 +873,71 @@ namespace WixToolset.Core.WindowsInstaller.Bind
873 row.Attributes = attributes;
874 }
875
905 - private void AddWixActionTuple(WixActionTuple tuple, WindowsInstallerData output)
876 + private void AddWixActionTuple(WixActionTuple tuple)
877 {
878 // Get the table definition for the action (and ensure the proper table exists for a module).
908 - TableDefinition sequenceTableDefinition = null;
879 + string sequenceTableName = null;
880 switch (tuple.SequenceTable)
881 {
882 case SequenceTable.AdminExecuteSequence:
912 - if (OutputType.Module == output.Type)
883 + if (OutputType.Module == this.Output.Type)
884 {
914 - output.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]);
915 - sequenceTableDefinition = this.TableDefinitions["ModuleAdminExecuteSequence"];
885 + this.Output.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]);
886 + sequenceTableName = "ModuleAdminExecuteSequence";
887 }
888 else
889 {
919 - sequenceTableDefinition = this.TableDefinitions["AdminExecuteSequence"];
890 + sequenceTableName = "AdminExecuteSequence";
891 }
892 break;
893 case SequenceTable.AdminUISequence:
923 - if (OutputType.Module == output.Type)
894 + if (OutputType.Module == this.Output.Type)
895 {
925 - output.EnsureTable(this.TableDefinitions["AdminUISequence"]);
926 - sequenceTableDefinition = this.TableDefinitions["ModuleAdminUISequence"];
896 + this.Output.EnsureTable(this.TableDefinitions["AdminUISequence"]);
897 + sequenceTableName = "ModuleAdminUISequence";
898 }
899 else
900 {
930 - sequenceTableDefinition = this.TableDefinitions["AdminUISequence"];
901 + sequenceTableName = "AdminUISequence";
902 }
903 break;
904 case SequenceTable.AdvertiseExecuteSequence:
934 - if (OutputType.Module == output.Type)
905 + if (OutputType.Module == this.Output.Type)
906 {
936 - output.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]);
937 - sequenceTableDefinition = this.TableDefinitions["ModuleAdvtExecuteSequence"];
907 + this.Output.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]);
908 + sequenceTableName = "ModuleAdvtExecuteSequence";
909 }
910 else
911 {
941 - sequenceTableDefinition = this.TableDefinitions["AdvtExecuteSequence"];
912 + sequenceTableName = "AdvtExecuteSequence";
913 }
914 break;
915 case SequenceTable.InstallExecuteSequence:
945 - if (OutputType.Module == output.Type)
916 + if (OutputType.Module == this.Output.Type)
917 {
947 - output.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]);
948 - sequenceTableDefinition = this.TableDefinitions["ModuleInstallExecuteSequence"];
918 + this.Output.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]);
919 + sequenceTableName = "ModuleInstallExecuteSequence";
920 }
921 else
922 {
952 - sequenceTableDefinition = this.TableDefinitions["InstallExecuteSequence"];
923 + sequenceTableName = "InstallExecuteSequence";
924 }
925 break;
926 case SequenceTable.InstallUISequence:
956 - if (OutputType.Module == output.Type)
927 + if (OutputType.Module == this.Output.Type)
928 {
958 - output.EnsureTable(this.TableDefinitions["InstallUISequence"]);
959 - sequenceTableDefinition = this.TableDefinitions["ModuleInstallUISequence"];
929 + this.Output.EnsureTable(this.TableDefinitions["InstallUISequence"]);
930 + sequenceTableName = "ModuleInstallUISequence";
931 }
932 else
933 {
963 - sequenceTableDefinition = this.TableDefinitions["InstallUISequence"];
934 + sequenceTableName = "InstallUISequence";
935 }
936 break;
937 }
938
939 // create the action sequence row in the output
969 - var sequenceTable = output.EnsureTable(sequenceTableDefinition);
970 - var row = sequenceTable.CreateRow(tuple.SourceLineNumbers);
940 + var row = this.CreateRow(tuple, sequenceTableName);
941
942 if (SectionType.Module == this.Section.Type)
943 {
@@ -992,7 +962,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
962 }
963 }
964
995 - private void AddWixCustomRowTuple(WixCustomRowTuple tuple, WindowsInstallerData output)
965 + private void AddWixCustomRowTuple(WixCustomRowTuple tuple)
966 {
967 var customTableDefinition = this.TableDefinitions[tuple.Table];
968
@@ -1002,8 +972,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
972 return;
973 }
974
1005 - var customTable = output.EnsureTable(customTableDefinition);
1006 - var customRow = customTable.CreateRow(tuple.SourceLineNumbers);
975 + var customRow = this.CreateRow(tuple, customTableDefinition);
976
977 #if TODO // SectionId seems like a good thing to preserve.
978 customRow.SectionId = tuple.SectionId;
@@ -1073,16 +1042,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1042 }
1043 }
1044
1076 - private void AddWixEnsureTableTuple(WixEnsureTableTuple tuple, WindowsInstallerData output)
1045 + private void AddWixEnsureTableTuple(WixEnsureTableTuple tuple)
1046 {
1047 var tableDefinition = this.TableDefinitions[tuple.Table];
1079 - output.EnsureTable(tableDefinition);
1048 + this.Output.EnsureTable(tableDefinition);
1049 }
1050
1082 - private void AddWixMediaTemplateTuple(WixMediaTemplateTuple tuple, WindowsInstallerData output)
1051 + private void AddWixMediaTemplateTuple(WixMediaTemplateTuple tuple)
1052 {
1084 - var table = output.EnsureTable(this.TableDefinitions["WixMediaTemplate"]);
1085 - var row = (WixMediaTemplateRow)table.CreateRow(tuple.SourceLineNumbers);
1053 + var row = (WixMediaTemplateRow)this.CreateRow(tuple, "WixMediaTemplate");
1054 row.CabinetTemplate = tuple.CabinetTemplate;
1055 row.CompressionLevel = tuple.CompressionLevel;
1056 row.DiskPrompt = tuple.DiskPrompt;
@@ -1091,26 +1059,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1059 row.MaximumCabinetSizeForLargeFileSplitting = tuple.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting;
1060 }
1061
1094 - private void AddTupleFromExtension(IntermediateTuple tuple, WindowsInstallerData output)
1062 + private void AddTupleFromExtension(IntermediateTuple tuple)
1063 {
1064 foreach (var extension in this.BackendExtensions)
1065 {
1098 - if (extension.TryAddTupleToOutput(tuple, output))
1066 + if (extension.TryAddTupleToOutput(tuple, this.Output))
1067 {
1068 break;
1069 }
1070 }
1071 }
1072
1105 - private void AddTupleDefaultly(IntermediateTuple tuple, WindowsInstallerData output, bool idIsPrimaryKey = false, string tableName = null)
1073 + private void AddTupleDefaultly(IntermediateTuple tuple, bool idIsPrimaryKey = false, string tableName = null)
1074 {
1075 if (!this.TableDefinitions.TryGet(tableName ?? tuple.Definition.Name, out var tableDefinition))
1076 {
1077 return;
1078 }
1079
1112 - var table = output.EnsureTable(tableDefinition);
1113 - var row = table.CreateRow(tuple.SourceLineNumbers);
1080 + var row = this.CreateRow(tuple, tableDefinition);
1081 var rowOffset = 0;
1082
1083 if (idIsPrimaryKey)
@@ -1159,6 +1126,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1126 }
1127 }
1128
1129 + private Row CreateRow(IntermediateTuple tuple, string tableDefinitionName) => this.CreateRow(tuple, this.TableDefinitions[tableDefinitionName]);
1130 +
1131 + private Row CreateRow(IntermediateTuple tuple, TableDefinition tableDefinition)
1132 + {
1133 + var table = this.Output.EnsureTable(tableDefinition);
1134 +
1135 + var row = table.CreateRow(tuple.SourceLineNumbers);
1136 + row.SectionId = this.Section.Id;
1137 +
1138 + return row;
1139 + }
1140 +
1141 private static string GetMsiFilenameValue(string shortName, string longName)
1142 {
1143 if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase))
src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs new
+90
@@ -0,0 +1,90 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolset.Core.WindowsInstaller.Bind
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.IO;
8 + using System.Linq;
9 + using WixToolset.Core.WindowsInstaller.Msi;
10 + using WixToolset.Core.WindowsInstaller.Unbind;
11 + using WixToolset.Data;
12 + using WixToolset.Data.Tuples;
13 + using WixToolset.Data.WindowsInstaller;
14 + using WixToolset.Extensibility.Services;
15 +
16 + internal class CreatePatchTransformsCommand
17 + {
18 + public CreatePatchTransformsCommand(IMessaging messaging, Intermediate intermediate, string intermediateFolder)
19 + {
20 + this.Messaging = messaging;
21 + this.Intermediate = intermediate;
22 + this.IntermediateFolder = intermediateFolder;
23 + }
24 +
25 + private IMessaging Messaging { get; }
26 +
27 + private Intermediate Intermediate { get; }
28 +
29 + private string IntermediateFolder { get; }
30 +
31 + public IEnumerable<PatchTransform> PatchTransforms { get; private set; }
32 +
33 + public IEnumerable<PatchTransform> Execute()
34 + {
35 + var patchTransforms = new List<PatchTransform>();
36 +
37 + var tuples = this.Intermediate.Sections.SelectMany(s => s.Tuples).OfType<WixPatchBaselineTuple>();
38 +
39 + foreach (var tuple in tuples)
40 + {
41 + WindowsInstallerData transform;
42 +
43 + if (tuple.TransformFile is null)
44 + {
45 + var baselineData = this.GetData(tuple.BaselineFile.Path);
46 + var updateData = this.GetData(tuple.UpdateFile.Path);
47 +
48 + var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, false);
49 + transform = command.Execute();
50 + }
51 + else
52 + {
53 + var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path.
54 +
55 + var command = new UnbindTransformCommand(this.Messaging, tuple.TransformFile.Path, exportBasePath, this.IntermediateFolder);
56 + transform = command.Execute();
57 + }
58 +
59 + patchTransforms.Add(new PatchTransform(tuple.Id.Id, transform));
60 + }
61 +
62 + this.PatchTransforms = patchTransforms;
63 +
64 + return this.PatchTransforms;
65 + }
66 +
67 + private WindowsInstallerData GetData(string path)
68 + {
69 + var ext = Path.GetExtension(path);
70 +
71 + if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase))
72 + {
73 + using (var database = new Database(path, OpenDatabase.ReadOnly))
74 + {
75 + var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path.
76 +
77 + var isAdminImage = false; // TODO: need a better way to set this
78 +
79 + var command = new UnbindDatabaseCommand(this.Messaging, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true);
80 + return command.Execute();
81 + }
82 + }
83 + else // assume .wixpdb (or .wixout)
84 + {
85 + var data = WindowsInstallerData.Load(path, true);
86 + return data;
87 + }
88 + }
89 + }
90 +}
src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+20 -20
@@ -48,7 +48,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
48 {
49 var mergeModulesFileFacades = new List<FileFacade>();
50
51 - IMsmMerge2 merge = MsmInterop.GetMsmMerge();
51 + var merge = MsmInterop.GetMsmMerge();
52
53 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
54 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
@@ -57,11 +57,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
57 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
58 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
59 // is a lot more costly for the common cases.
60 - var indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.Id.Id, StringComparer.Ordinal);
60 + var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal);
61
62 foreach (var wixMergeRow in this.WixMergeTuples)
63 {
64 - bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
64 + var containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
65
66 // If the module has files and creating layout
67 if (containsFiles && !this.SuppressLayout)
@@ -75,21 +75,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
75
76 private bool CreateFacadesForMergeModuleFiles(WixMergeTuple wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades)
77 {
78 - bool containsFiles = false;
78 + var containsFiles = false;
79
80 try
81 {
82 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
83 - using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
83 + using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
84 {
85 if (db.TableExists("File") && db.TableExists("Component"))
86 {
87 - Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase);
87 + var uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase);
88
89 - using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
89 + using (var view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
90 {
91 // add each file row from the merge module into the file row collection (check for errors along the way)
92 - foreach (Record record in view.Records)
92 + foreach (var record in view.Records)
93 {
94 // NOTE: this is very tricky - the merge module file rows are not added to the
95 // file table because they should not be created via idt import. Instead, these
@@ -103,21 +103,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
103 var mergeModuleFileFacade = new FileFacade(true, fileTuple);
104
105 // If case-sensitive collision with another merge module or a user-authored file identifier.
106 - if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.Id.Id, out var collidingFacade))
106 + if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade))
107 {
108 - this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.File.Id.Id));
108 + this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id));
109 }
110 - else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.Id.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
110 + else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
111 {
112 - this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.File.Id.Id, collidingFacade.File.Id.Id));
112 + this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id));
113 }
114 else // no collision
115 {
116 mergeModulesFileFacades.Add(mergeModuleFileFacade);
117
118 // Keep updating the indexes as new rows are added.
119 - indexedFileFacades.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade);
120 - uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade);
119 + indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade);
120 + uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade);
121 }
122
123 containsFiles = true;
@@ -126,13 +126,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
126 }
127
128 // Get the summary information to detect the Schema
129 - using (SummaryInformation summaryInformation = new SummaryInformation(db))
129 + using (var summaryInformation = new SummaryInformation(db))
130 {
131 - string moduleInstallerVersionString = summaryInformation.GetProperty(14);
131 + var moduleInstallerVersionString = summaryInformation.GetProperty(14);
132
133 try
134 {
135 - int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
135 + var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
136 if (moduleInstallerVersion > this.OutputInstallerVersion)
137 {
138 this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion));
@@ -159,7 +159,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
159
160 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeTuple wixMergeRow)
161 {
162 - bool moduleOpen = false;
162 + var moduleOpen = false;
163 short mergeLanguage;
164
165 var mergeId = wixMergeRow.Id.Id;
@@ -180,10 +180,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
180 moduleOpen = true;
181
182 // extract the module cabinet, then explode all of the files to a temp directory
183 - string moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab");
183 + var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab");
184 merge.ExtractCAB(moduleCabPath);
185
186 - string mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId);
186 + var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId);
187 Directory.CreateDirectory(mergeIdPath);
188
189 try
src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+282 -253
@@ -5,367 +5,396 @@ namespace WixToolset.Core.WindowsInstaller.Bind
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 - using System.Globalization;
8 using System.IO;
9 using System.Text;
10 + using WixToolset.Core.WindowsInstaller.Msi;
11 using WixToolset.Data;
12 - using WixToolset.Extensibility;
12 using WixToolset.Data.WindowsInstaller;
14 - using WixToolset.Extensibility.Services;
13 + using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Data;
16 - using WixToolset.Core.WindowsInstaller.Msi;
15 + using WixToolset.Extensibility.Services;
16
18 - internal class GenerateDatabaseCommand
17 + internal class GenerateDatabaseCommand
18 {
20 - public int Codepage { private get; set; }
19 + public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IFileSystemExtension> fileSystemExtensions, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory)
20 + {
21 + this.Messaging = messaging;
22 + this.BackendHelper = backendHelper;
23 + this.Extensions = fileSystemExtensions;
24 + this.Data = data;
25 + this.OutputPath = outputPath;
26 + this.TableDefinitions = tableDefinitions;
27 + this.IntermediateFolder = intermediateFolder;
28 + this.Codepage = codepage;
29 + this.KeepAddedColumns = keepAddedColumns;
30 + this.SuppressAddingValidationRows = suppressAddingValidationRows;
31 + this.UseSubDirectory = useSubdirectory;
32 + }
33 +
34 + private int Codepage { get; }
35
22 - public IBackendHelper BackendHelper { private get; set; }
36 + private IBackendHelper BackendHelper { get; }
37
24 - public IEnumerable<IFileSystemExtension> Extensions { private get; set; }
38 + private IEnumerable<IFileSystemExtension> Extensions { get; }
39
40 /// <summary>
41 /// Whether to keep columns added in a transform.
42 /// </summary>
29 - public bool KeepAddedColumns { private get; set; }
43 + private bool KeepAddedColumns { get; }
44
31 - public IMessaging Messaging { private get; set; }
45 + private IMessaging Messaging { get; }
46
33 - public WindowsInstallerData Output { private get; set; }
47 + private WindowsInstallerData Data { get; }
48
35 - public string OutputPath { private get; set; }
49 + private string OutputPath { get; }
50
37 - public TableDefinitionCollection TableDefinitions { private get; set; }
51 + private TableDefinitionCollection TableDefinitions { get; }
52
39 - public string IntermediateFolder { private get; set; }
53 + private string IntermediateFolder { get; }
54
55 public List<ITrackedFile> GeneratedTemporaryFiles { get; } = new List<ITrackedFile>();
56
57 /// <summary>
58 /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.
59 /// </summary>
46 - public bool SuppressAddingValidationRows { private get; set; }
60 + private bool SuppressAddingValidationRows { get; }
61
48 - public bool UseSubDirectory { private get; set; }
62 + private bool UseSubDirectory { get; }
63
64 public void Execute()
65 {
66 // Add the _Validation rows.
67 if (!this.SuppressAddingValidationRows)
68 {
55 - var validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]);
56 -
57 - foreach (var table in this.Output.Tables)
58 - {
59 - if (!table.Definition.Unreal)
60 - {
61 - // Add the validation rows for this table.
62 - foreach (ColumnDefinition columnDef in table.Definition.Columns)
63 - {
64 - var row = validationTable.CreateRow(null);
65 -
66 - row[0] = table.Name;
67 -
68 - row[1] = columnDef.Name;
69 -
70 - if (columnDef.Nullable)
71 - {
72 - row[2] = "Y";
73 - }
74 - else
75 - {
76 - row[2] = "N";
77 - }
78 -
79 - if (columnDef.MinValue.HasValue)
80 - {
81 - row[3] = columnDef.MinValue.Value;
82 - }
83 -
84 - if (columnDef.MaxValue.HasValue)
85 - {
86 - row[4] = columnDef.MaxValue.Value;
87 - }
88 -
89 - row[5] = columnDef.KeyTable;
90 -
91 - if (columnDef.KeyColumn.HasValue)
92 - {
93 - row[6] = columnDef.KeyColumn.Value;
94 - }
95 -
96 - if (ColumnCategory.Unknown != columnDef.Category)
97 - {
98 - row[7] = columnDef.Category.ToString();
99 - }
100 -
101 - row[8] = columnDef.Possibilities;
102 -
103 - row[9] = columnDef.Description;
104 - }
105 - }
106 - }
69 + this.AddValidationRows();
70 }
71
109 - // Set the base directory.
72 var baseDirectory = this.IntermediateFolder;
73
74 if (this.UseSubDirectory)
75 {
114 - string filename = Path.GetFileNameWithoutExtension(this.OutputPath);
76 + var filename = Path.GetFileNameWithoutExtension(this.OutputPath);
77 baseDirectory = Path.Combine(baseDirectory, filename);
116 -
117 - // make sure the directory exists
118 - Directory.CreateDirectory(baseDirectory);
78 }
79
121 - var idtDirectory = Path.Combine(baseDirectory, "_idts");
122 - Directory.CreateDirectory(idtDirectory);
80 + var idtFolder = Path.Combine(baseDirectory, "_idts");
81
124 - try
82 + var type = OpenDatabase.CreateDirect;
83 +
84 + if (OutputType.Patch == this.Data.Type)
85 {
126 - OpenDatabase type = OpenDatabase.CreateDirect;
86 + type |= OpenDatabase.OpenPatchFile;
87 + }
88
128 - // set special flag for patch files
129 - if (OutputType.Patch == this.Output.Type)
130 - {
131 - type |= OpenDatabase.OpenPatchFile;
132 - }
89 + // Localize the codepage if a value was specified directly.
90 + if (-1 != this.Codepage)
91 + {
92 + this.Data.Codepage = this.Codepage;
93 + }
94
95 + try
96 + {
97 #if DEBUG
98 Console.WriteLine("Opening database at: {0}", this.OutputPath);
99 #endif
100
138 - // Localize the codepage if a value was specified directly.
139 - if (-1 != this.Codepage)
140 - {
141 - this.Output.Codepage = this.Codepage;
142 - }
143 -
101 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
102
146 - using (Database db = new Database(this.OutputPath, type))
103 + Directory.CreateDirectory(idtFolder);
104 +
105 + using (var db = new Database(this.OutputPath, type))
106 {
148 - // if we're not using the default codepage, import a new one into our
107 + // If we're not using the default codepage, import a new one into our
108 // database before we add any tables (or the tables would be added
109 // with the wrong codepage).
151 - if (0 != this.Output.Codepage)
110 + if (0 != this.Data.Codepage)
111 {
153 - this.SetDatabaseCodepage(db, this.Output.Codepage, idtDirectory);
112 + this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder);
113 }
114
156 - foreach (Table table in this.Output.Tables)
115 + this.ImportTables(db, idtFolder);
116 +
117 + // Insert substorages (usually transforms inside a patch or instance transforms in a package).
118 + this.ImportSubStorages(db);
119 +
120 + // We're good, commit the changes to the new database.
121 + db.Commit();
122 + }
123 + }
124 + catch (IOException e)
125 + {
126 + // TODO: this error message doesn't seem specific enough
127 + throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e);
128 + }
129 + }
130 +
131 + private void AddValidationRows()
132 + {
133 + var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]);
134 +
135 + foreach (var table in this.Data.Tables)
136 + {
137 + if (!table.Definition.Unreal)
138 + {
139 + // Add the validation rows for this table.
140 + foreach (var columnDef in table.Definition.Columns)
141 {
158 - Table importTable = table;
159 - bool hasBinaryColumn = false;
142 + var row = validationTable.CreateRow(null);
143 +
144 + row[0] = table.Name;
145
161 - // Skip all unreal tables other than _Streams.
162 - if (table.Definition.Unreal && "_Streams" != table.Name)
146 + row[1] = columnDef.Name;
147 +
148 + if (columnDef.Nullable)
149 {
164 - continue;
150 + row[2] = "Y";
151 + }
152 + else
153 + {
154 + row[2] = "N";
155 }
156
167 - // Do not put the _Validation table in patches, it is not needed.
168 - if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name)
157 + if (columnDef.MinValue.HasValue)
158 {
170 - continue;
159 + row[3] = columnDef.MinValue.Value;
160 }
161
173 - // The only way to import binary data is to copy it to a local subdirectory first.
174 - // To avoid this extra copying and perf hit, import an empty table with the same
175 - // definition and later import the binary data from source using records.
176 - foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
162 + if (columnDef.MaxValue.HasValue)
163 {
178 - if (ColumnType.Object == columnDefinition.Type)
179 - {
180 - importTable = new Table(table.Definition);
181 - hasBinaryColumn = true;
182 - break;
183 - }
164 + row[4] = columnDef.MaxValue.Value;
165 }
166
186 - // Create the table via IDT import.
187 - if ("_Streams" != importTable.Name)
167 + row[5] = columnDef.KeyTable;
168 +
169 + if (columnDef.KeyColumn.HasValue)
170 {
189 - try
190 - {
191 - var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Output.Codepage, idtDirectory, this.KeepAddedColumns);
192 - command.Execute();
171 + row[6] = columnDef.KeyColumn.Value;
172 + }
173
194 - var buildOutput = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary);
195 - this.GeneratedTemporaryFiles.Add(buildOutput);
174 + if (ColumnCategory.Unknown != columnDef.Category)
175 + {
176 + row[7] = columnDef.Category.ToString();
177 + }
178
197 - db.Import(command.IdtPath);
198 - }
199 - catch (WixInvalidIdtException)
200 - {
201 - // If ValidateRows finds anything it doesn't like, it throws
202 - importTable.ValidateRows();
179 + row[8] = columnDef.Possibilities;
180
204 - // Otherwise we rethrow the InvalidIdt
205 - throw;
206 - }
181 + row[9] = columnDef.Description;
182 + }
183 + }
184 + }
185 + }
186 +
187 + private void ImportTables(Database db, string idtDirectory)
188 + {
189 + foreach (var table in this.Data.Tables)
190 + {
191 + var importTable = table;
192 + var hasBinaryColumn = false;
193 +
194 + // Skip all unreal tables other than _Streams.
195 + if (table.Definition.Unreal && "_Streams" != table.Name)
196 + {
197 + continue;
198 + }
199 +
200 + // Do not put the _Validation table in patches, it is not needed.
201 + if (OutputType.Patch == this.Data.Type && "_Validation" == table.Name)
202 + {
203 + continue;
204 + }
205 +
206 + // The only way to import binary data is to copy it to a local subdirectory first.
207 + // To avoid this extra copying and perf hit, import an empty table with the same
208 + // definition and later import the binary data from source using records.
209 + foreach (var columnDefinition in table.Definition.Columns)
210 + {
211 + if (ColumnType.Object == columnDefinition.Type)
212 + {
213 + importTable = new Table(table.Definition);
214 + hasBinaryColumn = true;
215 + break;
216 + }
217 + }
218 +
219 + // Create the table via IDT import.
220 + if ("_Streams" != importTable.Name)
221 + {
222 + try
223 + {
224 + var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns);
225 + command.Execute();
226 +
227 + var buildOutput = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary);
228 + this.GeneratedTemporaryFiles.Add(buildOutput);
229 +
230 + db.Import(command.IdtPath);
231 + }
232 + catch (WixInvalidIdtException)
233 + {
234 + // If ValidateRows finds anything it doesn't like, it throws
235 + importTable.ValidateRows();
236 +
237 + // Otherwise we rethrow the InvalidIdt
238 + throw;
239 + }
240 + }
241 +
242 + // insert the rows via SQL query if this table contains object fields
243 + if (hasBinaryColumn)
244 + {
245 + var query = new StringBuilder("SELECT ");
246 +
247 + // Build the query for the view.
248 + var firstColumn = true;
249 + foreach (var columnDefinition in table.Definition.Columns)
250 + {
251 + if (columnDefinition.Unreal)
252 + {
253 + continue;
254 }
255
209 - // insert the rows via SQL query if this table contains object fields
210 - if (hasBinaryColumn)
256 + if (!firstColumn)
257 {
212 - StringBuilder query = new StringBuilder("SELECT ");
258 + query.Append(",");
259 + }
260
214 - // Build the query for the view.
215 - bool firstColumn = true;
216 - foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
261 + query.AppendFormat(" `{0}`", columnDefinition.Name);
262 + firstColumn = false;
263 + }
264 + query.AppendFormat(" FROM `{0}`", table.Name);
265 +
266 + using (var tableView = db.OpenExecuteView(query.ToString()))
267 + {
268 + // Import each row containing a stream
269 + foreach (var row in table.Rows)
270 + {
271 + using (var record = new Record(table.Definition.Columns.Length))
272 {
218 - if (!firstColumn)
273 + // Stream names are created by concatenating the name of the table with the values
274 + // of the primary key (delimited by periods).
275 + var streamName = new StringBuilder();
276 +
277 + // the _Streams table doesn't prepend the table name (or a period)
278 + if ("_Streams" != table.Name)
279 {
220 - query.Append(",");
280 + streamName.Append(table.Name);
281 }
282
223 - query.AppendFormat(" `{0}`", columnDefinition.Name);
224 - firstColumn = false;
225 - }
226 - query.AppendFormat(" FROM `{0}`", table.Name);
283 + var needStream = false;
284
228 - using (View tableView = db.OpenExecuteView(query.ToString()))
229 - {
230 - // Import each row containing a stream
231 - foreach (Row row in table.Rows)
285 + for (var i = 0; i < table.Definition.Columns.Length; i++)
286 {
233 - using (Record record = new Record(table.Definition.Columns.Length))
287 + var columnDefinition = table.Definition.Columns[i];
288 +
289 + if (columnDefinition.Unreal)
290 {
235 - StringBuilder streamName = new StringBuilder();
236 - bool needStream = false;
291 + continue;
292 + }
293 +
294 + switch (columnDefinition.Type)
295 + {
296 + case ColumnType.Localized:
297 + case ColumnType.Preserved:
298 + case ColumnType.String:
299 + var str = row.FieldAsString(i);
300
238 - // the _Streams table doesn't prepend the table name (or a period)
239 - if ("_Streams" != table.Name)
240 - {
241 - streamName.Append(table.Name);
242 - }
301 + if (columnDefinition.PrimaryKey)
302 + {
303 + if (0 < streamName.Length)
304 + {
305 + streamName.Append(".");
306 + }
307 +
308 + streamName.Append(str);
309 + }
310
244 - for (int i = 0; i < table.Definition.Columns.Length; i++)
245 - {
246 - ColumnDefinition columnDefinition = table.Definition.Columns[i];
311 + record.SetString(i + 1, str);
312 + break;
313 + case ColumnType.Number:
314 + record.SetInteger(i + 1, row.FieldAsInteger(i));
315 + break;
316
248 - switch (columnDefinition.Type)
317 + case ColumnType.Object:
318 + if (null != row[i])
319 {
250 - case ColumnType.Localized:
251 - case ColumnType.Preserved:
252 - case ColumnType.String:
253 - if (columnDefinition.PrimaryKey)
320 + needStream = true;
321 + try
322 + {
323 + record.SetStream(i + 1, row.FieldAsString(i));
324 + }
325 + catch (Win32Exception e)
326 + {
327 + if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
328 {
255 - if (0 < streamName.Length)
256 - {
257 - streamName.Append(".");
258 - }
259 - streamName.Append((string)row[i]);
329 + throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, row.FieldAsString(i)));
330 }
261 -
262 - record.SetString(i + 1, (string)row[i]);
263 - break;
264 - case ColumnType.Number:
265 - record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture));
266 - break;
267 - case ColumnType.Object:
268 - if (null != row[i])
331 + else
332 {
270 - needStream = true;
271 - try
272 - {
273 - record.SetStream(i + 1, (string)row[i]);
274 - }
275 - catch (Win32Exception e)
276 - {
277 - if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
278 - {
279 - throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, (string)row[i]));
280 - }
281 - else
282 - {
283 - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
284 - }
285 - }
333 + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
334 }
287 - break;
335 + }
336 }
289 - }
290 -
291 - // stream names are created by concatenating the name of the table with the values
292 - // of the primary key (delimited by periods)
293 - // check for a stream name that is more than 62 characters long (the maximum allowed length)
294 - if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length)
295 - {
296 - this.Messaging.Write(ErrorMessages.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
297 - }
298 - else // add the row to the database
299 - {
300 - tableView.Modify(ModifyView.Assign, record);
301 - }
337 + break;
338 }
339 }
304 - }
305 -
306 - // Remove rows from the _Streams table for wixpdbs.
307 - if ("_Streams" == table.Name)
308 - {
309 - table.Rows.Clear();
310 - }
311 - }
312 - }
340
314 - // Insert substorages (usually transforms inside a patch or instance transforms in a package).
315 - if (0 < this.Output.SubStorages.Count)
316 - {
317 - using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
318 - {
319 - foreach (SubStorage subStorage in this.Output.SubStorages)
320 - {
321 - string transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst"));
322 -
323 - // Bind the transform.
324 - this.BindTransform(subStorage.Data, transformFile);
325 -
326 - if (this.Messaging.EncounteredError)
341 + // check for a stream name that is more than 62 characters long (the maximum allowed length)
342 + if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length)
343 {
328 - continue;
344 + this.Messaging.Write(ErrorMessages.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
345 }
330 -
331 - // add the storage
332 - using (Record record = new Record(2))
346 + else // add the row to the database
347 {
334 - record.SetString(1, subStorage.Name);
335 - record.SetStream(2, transformFile);
336 - storagesView.Modify(ModifyView.Assign, record);
348 + tableView.Modify(ModifyView.Assign, record);
349 }
350 }
351 }
352 }
353
342 - // We're good, commit the changes to the new database.
343 - db.Commit();
354 + // Remove rows from the _Streams table for wixpdbs.
355 + if ("_Streams" == table.Name)
356 + {
357 + table.Rows.Clear();
358 + }
359 }
360 }
346 - catch (IOException e)
347 - {
348 - // TODO: this error message doesn't seem specific enough
349 - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e);
350 - }
361 }
362
353 - private void BindTransform(WindowsInstallerData transform, string outputPath)
363 + private void ImportSubStorages(Database db)
364 {
355 - var command = new BindTransformCommand();
356 - command.Messaging = this.Messaging;
357 - command.Extensions = this.Extensions;
358 - command.TempFilesLocation = this.IntermediateFolder;
359 - command.Transform = transform;
360 - command.OutputPath = outputPath;
361 - command.TableDefinitions = this.TableDefinitions;
362 - command.Execute();
365 + if (0 < this.Data.SubStorages.Count)
366 + {
367 + using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
368 + {
369 + foreach (var subStorage in this.Data.SubStorages)
370 + {
371 + var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst"));
372 +
373 + // Bind the transform.
374 + var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.Extensions, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions);
375 + command.Execute();
376 +
377 + if (this.Messaging.EncounteredError)
378 + {
379 + continue;
380 + }
381 +
382 + // Add the storage to the database.
383 + using (var record = new Record(2))
384 + {
385 + record.SetString(1, subStorage.Name);
386 + record.SetStream(2, transformFile);
387 + storagesView.Modify(ModifyView.Assign, record);
388 + }
389 + }
390 + }
391 + }
392 }
393
365 - private void SetDatabaseCodepage(Database db, int codepage, string idtDirectory)
394 + private void SetDatabaseCodepage(Database db, int codepage, string idtFolder)
395 {
367 - // write out the _ForceCodepage IDT file
368 - var idtPath = Path.Combine(idtDirectory, "_ForceCodepage.idt");
396 + // Write out the _ForceCodepage IDT file.
397 + var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt");
398 using (var idtFile = new StreamWriter(idtPath, false, Encoding.ASCII))
399 {
400 idtFile.WriteLine(); // dummy column name record
@@ -377,14 +406,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
406 var trackId = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary);
407 this.GeneratedTemporaryFiles.Add(trackId);
408
380 - // try to import the table into the MSI
409 + // Try to import the table into the MSI.
410 try
411 {
412 db.Import(idtPath);
413 }
414 catch (WixInvalidIdtException)
415 {
387 - // the IDT should be valid, so an invalid code page was given
416 + // The IDT should be valid, so an invalid code page was given.
417 throw new WixException(ErrorMessages.IllegalCodepage(codepage));
418 }
419 }
src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs new
+588
@@ -0,0 +1,588 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolset.Core.WindowsInstaller
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Globalization;
8 + using WixToolset.Core.WindowsInstaller.Msi;
9 + using WixToolset.Data;
10 + using WixToolset.Data.Tuples;
11 + using WixToolset.Data.WindowsInstaller;
12 + using WixToolset.Data.WindowsInstaller.Rows;
13 + using WixToolset.Extensibility;
14 + using WixToolset.Extensibility.Services;
15 +
16 + /// <summary>
17 + /// Creates a transform by diffing two outputs.
18 + /// </summary>
19 + public sealed class GenerateTransformCommand
20 + {
21 + private const char sectionDelimiter = '/';
22 + private readonly IMessaging messaging;
23 + private SummaryInformationStreams transformSummaryInfo;
24 +
25 + /// <summary>
26 + /// Instantiates a new Differ class.
27 + /// </summary>
28 + public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool showPedanticMessages)
29 + {
30 + this.messaging = messaging;
31 + this.TargetOutput = targetOutput;
32 + this.UpdatedOutput = updatedOutput;
33 + this.ShowPedanticMessages = showPedanticMessages;
34 + }
35 +
36 + private WindowsInstallerData TargetOutput { get; }
37 +
38 + private WindowsInstallerData UpdatedOutput { get; }
39 +
40 + private TransformFlags ValidationFlags { get; }
41 +
42 + /// <summary>
43 + /// Gets or sets the option to show pedantic messages.
44 + /// </summary>
45 + /// <value>The option to show pedantic messages.</value>
46 + private bool ShowPedanticMessages { get; }
47 +
48 + /// <summary>
49 + /// Gets or sets the option to suppress keeping special rows.
50 + /// </summary>
51 + /// <value>The option to suppress keeping special rows.</value>
52 + private bool SuppressKeepingSpecialRows { get; }
53 +
54 + /// <summary>
55 + /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output.
56 + /// </summary>
57 + /// <value>The option to keep all rows including unchanged rows.</value>
58 + private bool PreserveUnchangedRows { get; }
59 +
60 + public WindowsInstallerData Transform { get; private set; }
61 +
62 + /// <summary>
63 + /// Creates a transform by diffing two outputs.
64 + /// </summary>
65 + /// <param name="targetOutput">The target output.</param>
66 + /// <param name="updatedOutput">The updated output.</param>
67 + /// <param name="validationFlags"></param>
68 + /// <returns>The transform.</returns>
69 + public WindowsInstallerData Execute()
70 + {
71 + var targetOutput = this.TargetOutput;
72 + var updatedOutput = this.UpdatedOutput;
73 + var validationFlags = this.ValidationFlags;
74 +
75 + var transform = new WindowsInstallerData(null)
76 + {
77 + Type = OutputType.Transform,
78 + Codepage = updatedOutput.Codepage
79 + };
80 +
81 + this.transformSummaryInfo = new SummaryInformationStreams();
82 +
83 + // compare the codepages
84 + if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags))
85 + {
86 + this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage));
87 + if (null != updatedOutput.SourceLineNumbers)
88 + {
89 + this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers));
90 + }
91 + }
92 +
93 + // compare the output types
94 + if (targetOutput.Type != updatedOutput.Type)
95 + {
96 + throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString()));
97 + }
98 +
99 + // compare the contents of the tables
100 + foreach (var targetTable in targetOutput.Tables)
101 + {
102 + var updatedTable = updatedOutput.Tables[targetTable.Name];
103 + var operation = TableOperation.None;
104 +
105 + var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation);
106 +
107 + if (TableOperation.Drop == operation)
108 + {
109 + var droppedTable = transform.EnsureTable(targetTable.Definition);
110 + droppedTable.Operation = TableOperation.Drop;
111 + }
112 + else if (TableOperation.None == operation)
113 + {
114 + var modified = transform.EnsureTable(updatedTable.Definition);
115 + foreach (var row in rows)
116 + {
117 + modified.Rows.Add(row);
118 + }
119 + }
120 + }
121 +
122 + // added tables
123 + foreach (var updatedTable in updatedOutput.Tables)
124 + {
125 + if (null == targetOutput.Tables[updatedTable.Name])
126 + {
127 + var addedTable = transform.EnsureTable(updatedTable.Definition);
128 + addedTable.Operation = TableOperation.Add;
129 +
130 + foreach (var updatedRow in updatedTable.Rows)
131 + {
132 + updatedRow.Operation = RowOperation.Add;
133 + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
134 + addedTable.Rows.Add(updatedRow);
135 + }
136 + }
137 + }
138 +
139 + // set summary information properties
140 + if (!this.SuppressKeepingSpecialRows)
141 + {
142 + var summaryInfoTable = transform.Tables["_SummaryInformation"];
143 + this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags);
144 + }
145 +
146 + this.Transform = transform;
147 + return this.Transform;
148 + }
149 +
150 + /// <summary>
151 + /// Add a row to the <paramref name="index"/> using the primary key.
152 + /// </summary>
153 + /// <param name="index">The indexed rows.</param>
154 + /// <param name="row">The row to index.</param>
155 + private void AddIndexedRow(Dictionary<string, Row> index, Row row)
156 + {
157 + var primaryKey = row.GetPrimaryKey();
158 +
159 + if (null != primaryKey)
160 + {
161 + if (index.TryGetValue(primaryKey, out var collisionRow))
162 + {
163 + // Overriding WixActionRows have a primary key defined and take precedence in the index.
164 + if (row is WixActionRow actionRow)
165 + {
166 + // If the current row is not overridable, see if the indexed row is.
167 + if (!actionRow.Overridable)
168 + {
169 + if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable)
170 + {
171 + // The indexed key is overridable and should be replaced.
172 + index[primaryKey] = actionRow;
173 + }
174 + }
175 +
176 + // If we got this far, the row does not need to be indexed.
177 + return;
178 + }
179 +
180 + if (this.ShowPedanticMessages)
181 + {
182 + this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name));
183 + }
184 + }
185 + else
186 + {
187 + index.Add(primaryKey, row);
188 + }
189 + }
190 + else // use the string representation of the row as its primary key (it may not be unique)
191 + {
192 + // this is provided for compatibility with unreal tables with no primary key
193 + // all real tables must specify at least one column as the primary key
194 + primaryKey = row.ToString();
195 + index[primaryKey] = row;
196 + }
197 + }
198 +
199 + private bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow)
200 + {
201 + comparedRow = null;
202 +
203 + var keepRow = false;
204 +
205 + if (null == targetRow ^ null == updatedRow)
206 + {
207 + if (null == targetRow)
208 + {
209 + updatedRow.Operation = RowOperation.Add;
210 + comparedRow = updatedRow;
211 + }
212 + else if (null == updatedRow)
213 + {
214 + targetRow.Operation = RowOperation.Delete;
215 + targetRow.SectionId += sectionDelimiter;
216 +
217 + comparedRow = targetRow;
218 + keepRow = true;
219 + }
220 + }
221 + else // possibly modified
222 + {
223 + updatedRow.Operation = RowOperation.None;
224 + if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
225 + {
226 + // ignore rows that shouldn't be in a transform
227 + if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
228 + {
229 + updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
230 + comparedRow = updatedRow;
231 + keepRow = true;
232 + }
233 + }
234 + else
235 + {
236 + if (this.PreserveUnchangedRows)
237 + {
238 + keepRow = true;
239 + }
240 +
241 + for (var i = 0; i < updatedRow.Fields.Length; i++)
242 + {
243 + var columnDefinition = updatedRow.Fields[i].Column;
244 +
245 + if (columnDefinition.Unreal)
246 + {
247 + }
248 + else if (!columnDefinition.PrimaryKey)
249 + {
250 + var modified = false;
251 +
252 + if (i >= targetRow.Fields.Length)
253 + {
254 + columnDefinition.Added = true;
255 + modified = true;
256 + }
257 + else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
258 + {
259 + if (null == targetRow[i] ^ null == updatedRow[i])
260 + {
261 + modified = true;
262 + }
263 + else if (null != targetRow[i] && null != updatedRow[i])
264 + {
265 + modified = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i));
266 + }
267 + }
268 + else if (ColumnType.Preserved == columnDefinition.Type)
269 + {
270 + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i);
271 +
272 + // keep rows containing preserved fields so the historical data is available to the binder
273 + keepRow = !this.SuppressKeepingSpecialRows;
274 + }
275 + else if (ColumnType.Object == columnDefinition.Type)
276 + {
277 + var targetObjectField = (ObjectField)targetRow.Fields[i];
278 + var updatedObjectField = (ObjectField)updatedRow.Fields[i];
279 +
280 + updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex;
281 + updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri;
282 +
283 + // always keep a copy of the previous data even if they are identical
284 + // This makes diff.wixmst clean and easier to control patch logic
285 + updatedObjectField.PreviousData = (string)targetObjectField.Data;
286 +
287 + // always remember the unresolved data for target build
288 + updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData;
289 +
290 + // keep rows containing object fields so the files can be compared in the binder
291 + keepRow = !this.SuppressKeepingSpecialRows;
292 + }
293 + else
294 + {
295 + modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i));
296 + }
297 +
298 + if (modified)
299 + {
300 + if (null != updatedRow.Fields[i].PreviousData)
301 + {
302 + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i);
303 + }
304 +
305 + updatedRow.Fields[i].Modified = true;
306 + updatedRow.Operation = RowOperation.Modify;
307 + keepRow = true;
308 + }
309 + }
310 + }
311 +
312 + if (keepRow)
313 + {
314 + comparedRow = updatedRow;
315 + comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
316 + }
317 + }
318 + }
319 +
320 + return keepRow;
321 + }
322 +
323 + private List<Row> CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation)
324 + {
325 + var rows = new List<Row>();
326 + operation = TableOperation.None;
327 +
328 + // dropped tables
329 + if (null == updatedTable ^ null == targetTable)
330 + {
331 + if (null == targetTable)
332 + {
333 + operation = TableOperation.Add;
334 + rows.AddRange(updatedTable.Rows);
335 + }
336 + else if (null == updatedTable)
337 + {
338 + operation = TableOperation.Drop;
339 + }
340 + }
341 + else // possibly modified tables
342 + {
343 + var updatedPrimaryKeys = new Dictionary<string, Row>();
344 + var targetPrimaryKeys = new Dictionary<string, Row>();
345 +
346 + // compare the table definitions
347 + if (0 != targetTable.Definition.CompareTo(updatedTable.Definition))
348 + {
349 + // continue to the next table; may be more mismatches
350 + this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name));
351 + }
352 + else
353 + {
354 + this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys);
355 +
356 + // diff the target and updated rows
357 + foreach (var targetPrimaryKeyEntry in targetPrimaryKeys)
358 + {
359 + var targetPrimaryKey = targetPrimaryKeyEntry.Key;
360 + var targetRow = targetPrimaryKeyEntry.Value;
361 + updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow);
362 +
363 + var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared);
364 +
365 + if (keepRow)
366 + {
367 + rows.Add(compared);
368 + }
369 + }
370 +
371 + // find the inserted rows
372 + foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys)
373 + {
374 + var updatedPrimaryKey = updatedPrimaryKeyEntry.Key;
375 +
376 + if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey))
377 + {
378 + var updatedRow = updatedPrimaryKeyEntry.Value;
379 +
380 + updatedRow.Operation = RowOperation.Add;
381 + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
382 + rows.Add(updatedRow);
383 + }
384 + }
385 + }
386 + }
387 +
388 + return rows;
389 + }
390 +
391 + private void IndexPrimaryKeys(Table targetTable, Dictionary<string, Row> targetPrimaryKeys, Table updatedTable, Dictionary<string, Row> updatedPrimaryKeys)
392 + {
393 + // index the target rows
394 + foreach (var row in targetTable.Rows)
395 + {
396 + this.AddIndexedRow(targetPrimaryKeys, row);
397 +
398 + if ("Property" == targetTable.Name)
399 + {
400 + var id = row.FieldAsString(0);
401 +
402 + if ("ProductCode" == id)
403 + {
404 + this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1);
405 +
406 + if ("*" == this.transformSummaryInfo.TargetProductCode)
407 + {
408 + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers));
409 + }
410 + }
411 + else if ("ProductVersion" == id)
412 + {
413 + this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1);
414 + }
415 + else if ("UpgradeCode" == id)
416 + {
417 + this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1);
418 + }
419 + }
420 + else if ("_SummaryInformation" == targetTable.Name)
421 + {
422 + var id = row.FieldAsInteger(0);
423 +
424 + if (1 == id) // PID_CODEPAGE
425 + {
426 + this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1);
427 + }
428 + else if (7 == id) // PID_TEMPLATE
429 + {
430 + this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1);
431 + }
432 + else if (14 == id) // PID_PAGECOUNT
433 + {
434 + this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1);
435 + }
436 + }
437 + }
438 +
439 + // index the updated rows
440 + foreach (var row in updatedTable.Rows)
441 + {
442 + this.AddIndexedRow(updatedPrimaryKeys, row);
443 +
444 + if ("Property" == updatedTable.Name)
445 + {
446 + var id = row.FieldAsString(0);
447 +
448 + if ("ProductCode" == id)
449 + {
450 + this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1);
451 +
452 + if ("*" == this.transformSummaryInfo.UpdatedProductCode)
453 + {
454 + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers));
455 + }
456 + }
457 + else if ("ProductVersion" == id)
458 + {
459 + this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1);
460 + }
461 + }
462 + else if ("_SummaryInformation" == updatedTable.Name)
463 + {
464 + var id = row.FieldAsInteger(0);
465 +
466 + if (1 == id) // PID_CODEPAGE
467 + {
468 + this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1);
469 + }
470 + else if (7 == id) // PID_TEMPLATE
471 + {
472 + this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1);
473 + }
474 + else if (14 == id) // PID_PAGECOUNT
475 + {
476 + this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1);
477 + }
478 + }
479 + }
480 + }
481 +
482 + private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags)
483 + {
484 + // calculate the minimum version of MSI required to process the transform
485 + var minimumVersion = 100;
486 +
487 + if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin))
488 + {
489 + minimumVersion = Math.Max(targetMin, updatedMin);
490 + }
491 +
492 + var summaryRows = new Dictionary<int, Row>(summaryInfoTable.Rows.Count);
493 +
494 + foreach (var row in summaryInfoTable.Rows)
495 + {
496 + var id = row.FieldAsInteger(0);
497 +
498 + summaryRows[id] = row;
499 +
500 + if ((int)SummaryInformation.Transform.CodePage == id)
501 + {
502 + row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage;
503 + row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage;
504 + }
505 + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id)
506 + {
507 + row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
508 + }
509 + else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id)
510 + {
511 + row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
512 + }
513 + else if ((int)SummaryInformation.Transform.ProductCodes == id)
514 + {
515 + row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode);
516 + }
517 + else if ((int)SummaryInformation.Transform.InstallerRequirement == id)
518 + {
519 + row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
520 + }
521 + else if ((int)SummaryInformation.Transform.Security == id)
522 + {
523 + row[1] = "4";
524 + }
525 + }
526 +
527 + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage))
528 + {
529 + var summaryRow = summaryInfoTable.CreateRow(null);
530 + summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage;
531 + summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
532 + }
533 +
534 + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
535 + {
536 + var summaryRow = summaryInfoTable.CreateRow(null);
537 + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
538 + summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
539 + }
540 +
541 + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags))
542 + {
543 + var summaryRow = summaryInfoTable.CreateRow(null);
544 + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
545 + summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture);
546 + }
547 +
548 + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement))
549 + {
550 + var summaryRow = summaryInfoTable.CreateRow(null);
551 + summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement;
552 + summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
553 + }
554 +
555 + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security))
556 + {
557 + var summaryRow = summaryInfoTable.CreateRow(null);
558 + summaryRow[0] = (int)SummaryInformation.Transform.Security;
559 + summaryRow[1] = "4";
560 + }
561 + }
562 +
563 + private class SummaryInformationStreams
564 + {
565 + public string TargetSummaryInfoCodepage { get; set; }
566 +
567 + public string TargetPlatformAndLanguage { get; set; }
568 +
569 + public string TargetProductCode { get; set; }
570 +
571 + public string TargetProductVersion { get; set; }
572 +
573 + public string TargetUpgradeCode { get; set; }
574 +
575 + public string TargetMinimumVersion { get; set; }
576 +
577 + public string UpdatedSummaryInfoCodepage { get; set; }
578 +
579 + public string UpdatedPlatformAndLanguage { get; set; }
580 +
581 + public string UpdatedProductCode { get; set; }
582 +
583 + public string UpdatedProductVersion { get; set; }
584 +
585 + public string UpdatedMinimumVersion { get; set; }
586 + }
587 + }
588 +}
src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
+1 -1
@@ -132,7 +132,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
132 file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths);
133 }
134
135 -#if REVISIT_FOR_PATCHING
135 +#if TODO_PATCHING
136 Field field = row.Fields[2];
137 if (null != field.PreviousData)
138 {
src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs new
+585
@@ -0,0 +1,585 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolset.Core.WindowsInstaller.Bind
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.Diagnostics;
8 + using System.IO;
9 + using System.Linq;
10 + using WixToolset.Core.Bind;
11 + using WixToolset.Data;
12 + using WixToolset.Data.Tuples;
13 + using WixToolset.Data.WindowsInstaller;
14 + using WixToolset.Data.WindowsInstaller.Rows;
15 + using WixToolset.Extensibility;
16 + using WixToolset.Extensibility.Services;
17 +
18 + internal class GetFileFacadesFromTransforms
19 + {
20 + public GetFileFacadesFromTransforms(IMessaging messaging, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions)
21 + {
22 + this.Messaging = messaging;
23 + this.SubStorages = subStorages;
24 + this.TableDefinitions = tableDefinitions;
25 + }
26 +
27 + public IEnumerable<IFileSystemExtension> Extensions { get; }
28 +
29 + private IMessaging Messaging { get; }
30 +
31 + public IEnumerable<SubStorage> SubStorages { get; }
32 +
33 + private TableDefinitionCollection TableDefinitions { get; }
34 +
35 + public IEnumerable<FileFacade> FileFacades { get; private set; }
36 +
37 + public void Execute()
38 + {
39 + var allFileRows = new List<FileFacade>();
40 + var copyToPatch = (allFileRows != null);
41 +#if TODO_PATCHING
42 + var patchMediaRows = new RowDictionary<MediaRow>();
43 +
44 + var patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
45 +
46 + var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
47 + var patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
48 +
49 + if (copyFromPatch)
50 + {
51 + // index patch files by diskId+fileId
52 + foreach (WixFileRow patchFileRow in patchFileTable.Rows)
53 + {
54 + int diskId = patchFileRow.DiskId;
55 + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
56 + {
57 + mediaFileRows = new RowDictionary<WixFileRow>();
58 + patchMediaFileRows.Add(diskId, mediaFileRows);
59 + }
60 +
61 + mediaFileRows.Add(patchFileRow);
62 + }
63 +
64 + var patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
65 + patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable);
66 + }
67 +
68 + // Index paired transforms by name without their "#" prefix.
69 + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name.Substring(1), s => s.Data);
70 +
71 + foreach (var substorage in this.SubStorages)
72 + {
73 + if (substorage.Name.StartsWith("#"))
74 + {
75 + // Skip paired transforms.
76 + continue;
77 + }
78 +
79 + var mainTransform = substorage.Data;
80 + var mainWixFiles = new RowDictionary<WixFileRow>(mainTransform.Tables["WixFile"]);
81 + var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
82 +
83 + var mainFileTable = mainTransform.Tables["File"];
84 + var pairedTransform = pairedTransforms[substorage.Name];
85 +
86 + // copy Media.LastSequence and index the MsiFileHash table if it exists.
87 + if (copyFromPatch)
88 + {
89 + var pairedMediaTable = pairedTransform.Tables["Media"];
90 + foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
91 + {
92 + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
93 + pairedMediaRow.Fields[1] = patchMediaRow.Fields[1];
94 + }
95 +
96 + if (null != mainMsiFileHashTable)
97 + {
98 + mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable);
99 + }
100 +
101 + // Validate file row changes for keypath-related issues
102 + this.ValidateFileRowChanges(mainTransform);
103 + }
104 +
105 + if (null == mainFileTable)
106 + {
107 + continue;
108 + }
109 +
110 + // Index File table of pairedTransform
111 + var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
112 +
113 + foreach (FileRow mainFileRow in mainFileTable.Rows)
114 + {
115 + if (RowOperation.Delete == mainFileRow.Operation)
116 + {
117 + continue;
118 + }
119 + else if (RowOperation.None == mainFileRow.Operation)
120 + {
121 + continue;
122 + }
123 +
124 + var mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
125 +
126 + if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
127 + {
128 + var objectField = (ObjectField)mainWixFileRow.Fields[6];
129 + var pairedFileRow = pairedFileRows.Get(mainFileRow.File);
130 +
131 + // If the file is new, we always need to add it to the patch.
132 + if (mainFileRow.Operation != RowOperation.Add)
133 + {
134 + // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
135 + if (null == objectField.PreviousData)
136 + {
137 + if (mainFileRow.Operation == RowOperation.None)
138 + {
139 + continue;
140 + }
141 + }
142 + else
143 + {
144 + // TODO: should this entire condition be placed in the binder file manager?
145 + if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&
146 + !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
147 + {
148 + // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
149 + mainFileRow.Operation = RowOperation.Modify;
150 + if (null != pairedFileRow)
151 + {
152 + // Always patch-added, but never non-compressed.
153 + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
154 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
155 + pairedFileRow.Fields[6].Modified = true;
156 + pairedFileRow.Operation = RowOperation.Modify;
157 + }
158 + }
159 + else
160 + {
161 + // The File is same. We need mark all the attributes as unchanged.
162 + mainFileRow.Operation = RowOperation.None;
163 + foreach (var field in mainFileRow.Fields)
164 + {
165 + field.Modified = false;
166 + }
167 +
168 + if (null != pairedFileRow)
169 + {
170 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
171 + pairedFileRow.Fields[6].Modified = false;
172 + pairedFileRow.Operation = RowOperation.None;
173 + }
174 + continue;
175 + }
176 + }
177 + }
178 + else if (null != pairedFileRow) // RowOperation.Add
179 + {
180 + // Always patch-added, but never non-compressed.
181 + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
182 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
183 + pairedFileRow.Fields[6].Modified = true;
184 + pairedFileRow.Operation = RowOperation.Add;
185 + }
186 + }
187 +
188 + // index patch files by diskId+fileId
189 + int diskId = mainWixFileRow.DiskId;
190 +
191 + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
192 + {
193 + mediaFileRows = new RowDictionary<WixFileRow>();
194 + patchMediaFileRows.Add(diskId, mediaFileRows);
195 + }
196 +
197 + var fileId = mainFileRow.File;
198 + var patchFileRow = mediaFileRows.Get(fileId);
199 + if (copyToPatch)
200 + {
201 + if (null == patchFileRow)
202 + {
203 + var patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
204 + patchActualFileRow.CopyFrom(mainFileRow);
205 +
206 + patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
207 + patchFileRow.CopyFrom(mainWixFileRow);
208 +
209 + mediaFileRows.Add(patchFileRow);
210 +
211 + allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right?
212 + }
213 + else
214 + {
215 + // TODO: confirm the rest of data is identical?
216 +
217 + // make sure Source is same. Otherwise we are silently ignoring a file.
218 + if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase))
219 + {
220 + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
221 + }
222 +
223 + // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
224 + patchFileRow.AppendPreviousDataFrom(mainWixFileRow);
225 + }
226 + }
227 + //else
228 + //{
229 + // // copy data from the patch back to the transform
230 + // if (null != patchFileRow)
231 + // {
232 + // var pairedFileRow = pairedFileRows.Get(fileId);
233 + // for (var i = 0; i < patchFileRow.Fields.Length; i++)
234 + // {
235 + // var patchValue = patchFileRow[i] == null ? String.Empty : patchFileRow.FieldAsString(i);
236 + // var mainValue = mainFileRow[i] == null ? String.Empty : mainFileRow.FieldAsString(i);
237 +
238 + // if (1 == i)
239 + // {
240 + // // File.Component_ changes should not come from the shared file rows
241 + // // that contain the file information as each individual transform might
242 + // // have different changes (or no changes at all).
243 + // }
244 + // // File.Attributes should not changed for binary deltas
245 + // else if (6 == i)
246 + // {
247 + // if (null != patchFileRow.Patch)
248 + // {
249 + // // File.Attribute should not change for binary deltas
250 + // pairedFileRow.Attributes = mainFileRow.Attributes;
251 + // mainFileRow.Fields[i].Modified = false;
252 + // }
253 + // }
254 + // // File.Sequence is updated in pairedTransform, not mainTransform
255 + // else if (7 == i)
256 + // {
257 + // // file sequence is updated in Patch table instead of File table for delta patches
258 + // if (null != patchFileRow.Patch)
259 + // {
260 + // pairedFileRow.Fields[i].Modified = false;
261 + // }
262 + // else
263 + // {
264 + // pairedFileRow[i] = patchFileRow[i];
265 + // pairedFileRow.Fields[i].Modified = true;
266 + // }
267 + // mainFileRow.Fields[i].Modified = false;
268 + // }
269 + // else if (patchValue != mainValue)
270 + // {
271 + // mainFileRow[i] = patchFileRow[i];
272 + // mainFileRow.Fields[i].Modified = true;
273 + // if (mainFileRow.Operation == RowOperation.None)
274 + // {
275 + // mainFileRow.Operation = RowOperation.Modify;
276 + // }
277 + // }
278 + // }
279 +
280 + // // copy MsiFileHash row for this File
281 + // if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow))
282 + // {
283 + // patchHashRow = patchFileRow.Hash;
284 + // }
285 +
286 + // if (null != patchHashRow)
287 + // {
288 + // var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
289 + // var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
290 + // for (var i = 0; i < patchHashRow.Fields.Length; i++)
291 + // {
292 + // mainHashRow[i] = patchHashRow[i];
293 + // if (i > 1)
294 + // {
295 + // // assume all hash fields have been modified
296 + // mainHashRow.Fields[i].Modified = true;
297 + // }
298 + // }
299 +
300 + // // assume the MsiFileHash operation follows the File one
301 + // mainHashRow.Operation = mainFileRow.Operation;
302 + // }
303 +
304 + // // copy MsiAssemblyName rows for this File
305 + // List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
306 + // if (null != patchAssemblyNameRows)
307 + // {
308 + // var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
309 + // foreach (var patchAssemblyNameRow in patchAssemblyNameRows)
310 + // {
311 + // // Copy if there isn't an identical modified/added row already in the transform.
312 + // var foundMatchingModifiedRow = false;
313 + // foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows)
314 + // {
315 + // if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
316 + // {
317 + // foundMatchingModifiedRow = true;
318 + // break;
319 + // }
320 + // }
321 +
322 + // if (!foundMatchingModifiedRow)
323 + // {
324 + // var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
325 + // for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
326 + // {
327 + // mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
328 + // }
329 +
330 + // // assume value field has been modified
331 + // mainAssemblyNameRow.Fields[2].Modified = true;
332 + // mainAssemblyNameRow.Operation = mainFileRow.Operation;
333 + // }
334 + // }
335 + // }
336 +
337 + // // Add patch header for this file
338 + // if (null != patchFileRow.Patch)
339 + // {
340 + // // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
341 + // this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
342 + // this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
343 +
344 + // // Add to Patch table
345 + // var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
346 + // if (0 == patchTable.Rows.Count)
347 + // {
348 + // patchTable.Operation = TableOperation.Add;
349 + // }
350 +
351 + // var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
352 + // patchRow[0] = patchFileRow.File;
353 + // patchRow[1] = patchFileRow.Sequence;
354 +
355 + // var patchFile = new FileInfo(patchFileRow.Source);
356 + // patchRow[2] = (int)patchFile.Length;
357 + // patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
358 +
359 + // var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
360 + // if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length)
361 + // {
362 + // streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
363 +
364 + // var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
365 + // if (0 == patchHeadersTable.Rows.Count)
366 + // {
367 + // patchHeadersTable.Operation = TableOperation.Add;
368 + // }
369 +
370 + // var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
371 + // patchHeadersRow[0] = streamName;
372 + // patchHeadersRow[1] = patchFileRow.Patch;
373 + // patchRow[5] = streamName;
374 + // patchHeadersRow.Operation = RowOperation.Add;
375 + // }
376 + // else
377 + // {
378 + // patchRow[4] = patchFileRow.Patch;
379 + // }
380 + // patchRow.Operation = RowOperation.Add;
381 + // }
382 + // }
383 + // else
384 + // {
385 + // // TODO: throw because all transform rows should have made it into the patch
386 + // }
387 + //}
388 + }
389 + }
390 +#endif
391 + this.FileFacades = allFileRows;
392 + }
393 +
394 + /// <summary>
395 + /// Adds the PatchFiles action to the sequence table if it does not already exist.
396 + /// </summary>
397 + /// <param name="table">The sequence table to check or modify.</param>
398 + /// <param name="mainTransform">The primary authoring transform.</param>
399 + /// <param name="pairedTransform">The secondary patch transform.</param>
400 + /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
401 + private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow)
402 + {
403 + var tableName = table.ToString();
404 +
405 + // Find/add PatchFiles action (also determine sequence for it).
406 + // Search mainTransform first, then pairedTransform (pairedTransform overrides).
407 + var hasPatchFilesAction = false;
408 + var installFilesSequence = 0;
409 + var duplicateFilesSequence = 0;
410 +
411 + TestSequenceTableForPatchFilesAction(
412 + mainTransform.Tables[tableName],
413 + ref hasPatchFilesAction,
414 + ref installFilesSequence,
415 + ref duplicateFilesSequence);
416 + TestSequenceTableForPatchFilesAction(
417 + pairedTransform.Tables[tableName],
418 + ref hasPatchFilesAction,
419 + ref installFilesSequence,
420 + ref duplicateFilesSequence);
421 + if (!hasPatchFilesAction)
422 + {
423 + WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionTuple);
424 +
425 + var sequence = patchFilesActionTuple.Sequence;
426 +
427 + // Test for default sequence value's appropriateness
428 + if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence))
429 + {
430 + if (0 != duplicateFilesSequence)
431 + {
432 + if (duplicateFilesSequence < installFilesSequence)
433 + {
434 + throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
435 + }
436 + else
437 + {
438 + sequence = (duplicateFilesSequence + installFilesSequence) / 2;
439 + if (installFilesSequence == sequence || duplicateFilesSequence == sequence)
440 + {
441 + throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
442 + }
443 + }
444 + }
445 + else
446 + {
447 + sequence = installFilesSequence + 1;
448 + }
449 + }
450 +
451 + var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
452 + if (0 == sequenceTable.Rows.Count)
453 + {
454 + sequenceTable.Operation = TableOperation.Add;
455 + }
456 +
457 + var patchAction = sequenceTable.CreateRow(null);
458 + patchAction[0] = patchFilesActionTuple.Action;
459 + patchAction[1] = patchFilesActionTuple.Condition;
460 + patchAction[2] = sequence;
461 + patchAction.Operation = RowOperation.Add;
462 + }
463 + }
464 +
465 + /// <summary>
466 + /// Tests sequence table for PatchFiles and associated actions
467 + /// </summary>
468 + /// <param name="sequenceTable">The table to test.</param>
469 + /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
470 + /// <param name="installFilesSequence">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
471 + /// <param name="duplicateFilesSequence">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
472 + private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence)
473 + {
474 + if (null != sequenceTable)
475 + {
476 + foreach (var row in sequenceTable.Rows)
477 + {
478 + var actionName = row.FieldAsString(0);
479 + switch (actionName)
480 + {
481 + case "PatchFiles":
482 + hasPatchFilesAction = true;
483 + break;
484 +
485 + case "InstallFiles":
486 + installFilesSequence = row.FieldAsInteger(2);
487 + break;
488 +
489 + case "DuplicateFiles":
490 + duplicateFilesSequence = row.FieldAsInteger(2);
491 + break;
492 + }
493 + }
494 + }
495 + }
496 +
497 + /// <summary>
498 + /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
499 + /// </summary>
500 + /// <param name="output">The output to validate.</param>
501 + private void ValidateFileRowChanges(WindowsInstallerData transform)
502 + {
503 + var componentTable = transform.Tables["Component"];
504 + var fileTable = transform.Tables["File"];
505 +
506 + // There's no sense validating keypaths if the transform has no component or file table
507 + if (componentTable == null || fileTable == null)
508 + {
509 + return;
510 + }
511 +
512 + var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
513 +
514 + // Index the Component table for non-directory & non-registry key paths.
515 + foreach (var row in componentTable.Rows)
516 + {
517 + var keyPath = row.FieldAsString(5);
518 + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
519 + {
520 + componentKeyPath.Add(row.FieldAsString(0), keyPath);
521 + }
522 + }
523 +
524 + var componentWithChangedKeyPath = new Dictionary<string, string>();
525 + var componentWithNonKeyPathChanged = new Dictionary<string, string>();
526 + // Verify changes in the file table, now that file diffing has occurred
527 + foreach (FileRow row in fileTable.Rows)
528 + {
529 + if (RowOperation.Modify != row.Operation)
530 + {
531 + continue;
532 + }
533 +
534 + var fileId = row.FieldAsString(0);
535 + var componentId = row.FieldAsString(1);
536 +
537 + // If this file is the keypath of a component
538 + if (componentKeyPath.ContainsValue(fileId))
539 + {
540 + if (!componentWithChangedKeyPath.ContainsKey(componentId))
541 + {
542 + componentWithChangedKeyPath.Add(componentId, fileId);
543 + }
544 + }
545 + else
546 + {
547 + if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
548 + {
549 + componentWithNonKeyPathChanged.Add(componentId, fileId);
550 + }
551 + }
552 + }
553 +
554 + foreach (var componentFile in componentWithNonKeyPathChanged)
555 + {
556 + // Make sure all changes to non keypath files also had a change in the keypath.
557 + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath))
558 + {
559 + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath));
560 + }
561 + }
562 + }
563 +
564 + private bool CompareFiles(string targetFile, string updatedFile)
565 + {
566 + bool? compared = null;
567 + foreach (var extension in this.Extensions)
568 + {
569 + compared = extension.CompareFiles(targetFile, updatedFile);
570 +
571 + if (compared.HasValue)
572 + {
573 + break;
574 + }
575 + }
576 +
577 + if (!compared.HasValue)
578 + {
579 + throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
580 + }
581 +
582 + return compared.Value;
583 + }
584 + }
585 +}
src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs
+18 -3
@@ -9,15 +9,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind
9 using WixToolset.Data;
10 using WixToolset.Data.Tuples;
11 using WixToolset.Data.WindowsInstaller;
12 + using WixToolset.Extensibility;
13
14 internal class LoadTableDefinitionsCommand
15 {
15 - public LoadTableDefinitionsCommand(IntermediateSection section) => this.Section = section;
16 -
17 - public TableDefinitionCollection TableDefinitions { get; private set; }
16 + public LoadTableDefinitionsCommand(IntermediateSection section, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions)
17 + {
18 + this.Section = section;
19 + this.BackendExtensions = backendExtensions;
20 + }
21
22 private IntermediateSection Section { get; }
23
24 + private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
25 +
26 + public TableDefinitionCollection TableDefinitions { get; private set; }
27 +
28 public TableDefinitionCollection Execute()
29 {
30 var tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandardInternal.GetTableDefinitions());
@@ -28,6 +35,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
35 tableDefinitions.Add(customTableDefinition);
36 }
37
38 + foreach (var backendExtension in this.BackendExtensions)
39 + {
40 + foreach (var tableDefinition in backendExtension.TableDefinitions)
41 + {
42 + tableDefinitions.Add(tableDefinition);
43 + }
44 + }
45 +
46 this.TableDefinitions = tableDefinitions;
47 return this.TableDefinitions;
48 }
src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+4 -4
@@ -277,7 +277,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
277
278 using (Record record = new Record(1))
279 {
280 - record.SetString(1, file.File.Id.Id);
280 + record.SetString(1, file.Id);
281 view.Execute(record);
282 }
283
@@ -288,7 +288,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
288 throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module.");
289 }
290
291 - recordUpdate.SetInteger(1, file.File.Sequence);
291 + recordUpdate.SetInteger(1, file.Sequence);
292
293 // Update the file attributes to match the compression specified
294 // on the Merge element or on the Package element.
@@ -300,12 +300,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
300 attributes = recordUpdate.GetInteger(2);
301 }
302
303 - if ((file.File.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed)
303 + if (file.Compressed)
304 {
305 attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed;
306 attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
307 }
308 - else if ((file.File.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed)
308 + else if (file.Uncompressed)
309 {
310 attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
311 attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed;
src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs new
+246
@@ -0,0 +1,246 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolset.Core.WindowsInstaller.Bind
4 +{
5 + using System;
6 + using System.Collections;
7 + using System.Globalization;
8 + using System.Text;
9 + using System.Text.RegularExpressions;
10 + using WixToolset.Data;
11 + using WixToolset.Data.WindowsInstaller;
12 + using WixToolset.Extensibility;
13 +
14 + internal class PatchTransform
15 + {
16 + public PatchTransform(string baseline, WindowsInstallerData transform)
17 + {
18 + this.Baseline = baseline;
19 + this.Transform = transform;
20 + }
21 +
22 + public string Baseline { get; }
23 +
24 + public WindowsInstallerData Transform { get; }
25 +
26 + /// <summary>
27 + /// Validates that the differences in the transform are valid for patch transforms.
28 + /// </summary>
29 + public void Validate()
30 + {
31 +#if TODO_PATCHING
32 + // Changing the ProdocutCode in a patch transform is not recommended.
33 + Table propertyTable = this.Transform.Tables["Property"];
34 + if (null != propertyTable)
35 + {
36 + foreach (Row row in propertyTable.Rows)
37 + {
38 + // Only interested in modified rows; fast check.
39 + if (RowOperation.Modify == row.Operation)
40 + {
41 + if (0 == String.CompareOrdinal("ProductCode", (string)row[0]))
42 + {
43 + this.OnMessage(WixWarnings.MajorUpgradePatchNotRecommended());
44 + }
45 + }
46 + }
47 + }
48 +
49 + // If there is nothing in the component table we can return early because the remaining checks are component based.
50 + Table componentTable = this.Transform.Tables["Component"];
51 + if (null == componentTable)
52 + {
53 + return;
54 + }
55 +
56 + // Index Feature table row operations
57 + Table featureTable = this.Transform.Tables["Feature"];
58 + Table featureComponentsTable = this.Transform.Tables["FeatureComponents"];
59 + Hashtable featureOps = null;
60 + if (null != featureTable)
61 + {
62 + int capacity = featureTable.Rows.Count;
63 + featureOps = new Hashtable(capacity);
64 +
65 + foreach (Row row in featureTable.Rows)
66 + {
67 + featureOps[(string)row[0]] = row.Operation;
68 + }
69 + }
70 + else
71 + {
72 + featureOps = new Hashtable();
73 + }
74 +
75 + // Index Component table and check for keypath modifications
76 + Hashtable deletedComponent = new Hashtable();
77 + Hashtable componentKeyPath = new Hashtable();
78 + foreach (Row row in componentTable.Rows)
79 + {
80 + string id = row.Fields[0].Data.ToString();
81 + string keypath = (null == row.Fields[5].Data) ? String.Empty : row.Fields[5].Data.ToString();
82 +
83 + componentKeyPath.Add(id, keypath);
84 + if (RowOperation.Delete == row.Operation)
85 + {
86 + deletedComponent.Add(id, row);
87 + }
88 + else if (RowOperation.Modify == row.Operation)
89 + {
90 + if (row.Fields[1].Modified)
91 + {
92 + // Changing the guid of a component is equal to deleting the old one and adding a new one.
93 + deletedComponent.Add(id, row);
94 + }
95 +
96 + // If the keypath is modified its an error
97 + if (row.Fields[5].Modified)
98 + {
99 + this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, id, this.transformPath));
100 + }
101 + }
102 + }
103 +
104 + // Verify changes in the file table
105 + Table fileTable = this.Transform.Tables["File"];
106 + if (null != fileTable)
107 + {
108 + Hashtable componentWithChangedKeyPath = new Hashtable();
109 + foreach (Row row in fileTable.Rows)
110 + {
111 + if (RowOperation.None != row.Operation)
112 + {
113 + string fileId = row.Fields[0].Data.ToString();
114 + string componentId = row.Fields[1].Data.ToString();
115 +
116 + // If this file is the keypath of a component
117 + if (String.Equals((string)componentKeyPath[componentId], fileId, StringComparison.Ordinal))
118 + {
119 + if (row.Fields[2].Modified)
120 + {
121 + // You cant change the filename of a file that is the keypath of a component.
122 + this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, componentId, this.transformPath));
123 + }
124 +
125 + if (!componentWithChangedKeyPath.ContainsKey(componentId))
126 + {
127 + componentWithChangedKeyPath.Add(componentId, fileId);
128 + }
129 + }
130 +
131 + if (RowOperation.Delete == row.Operation)
132 + {
133 + // If the file is removed from a component that is not deleted.
134 + if (!deletedComponent.ContainsKey(componentId))
135 + {
136 + bool foundRemoveFileEntry = false;
137 + string filename = Common.GetName((string)row[2], false, true);
138 +
139 + Table removeFileTable = this.Transform.Tables["RemoveFile"];
140 + if (null != removeFileTable)
141 + {
142 + foreach (Row removeFileRow in removeFileTable.Rows)
143 + {
144 + if (RowOperation.Delete == removeFileRow.Operation)
145 + {
146 + continue;
147 + }
148 +
149 + if (componentId == (string)removeFileRow[1])
150 + {
151 + // Check if there is a RemoveFile entry for this file
152 + if (null != removeFileRow[2])
153 + {
154 + string removeFileName = Common.GetName((string)removeFileRow[2], false, true);
155 +
156 + // Convert the MSI format for a wildcard string to Regex format.
157 + removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\.");
158 +
159 + Regex regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
160 + if (regex.IsMatch(filename))
161 + {
162 + foundRemoveFileEntry = true;
163 + break;
164 + }
165 + }
166 + }
167 + }
168 + }
169 +
170 + if (!foundRemoveFileEntry)
171 + {
172 + this.OnMessage(WixWarnings.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId));
173 + }
174 + }
175 + }
176 + }
177 + }
178 + }
179 +
180 + if (0 < deletedComponent.Count)
181 + {
182 + // Index FeatureComponents table.
183 + Hashtable featureComponents = new Hashtable();
184 +
185 + if (null != featureComponentsTable)
186 + {
187 + foreach (Row row in featureComponentsTable.Rows)
188 + {
189 + ArrayList features;
190 + string componentId = row.Fields[1].Data.ToString();
191 +
192 + if (featureComponents.Contains(componentId))
193 + {
194 + features = (ArrayList)featureComponents[componentId];
195 + }
196 + else
197 + {
198 + features = new ArrayList();
199 + featureComponents.Add(componentId, features);
200 + }
201 + features.Add(row.Fields[0].Data.ToString());
202 + }
203 + }
204 +
205 + // Check to make sure if a component was deleted, the feature was too.
206 + foreach (DictionaryEntry entry in deletedComponent)
207 + {
208 + if (featureComponents.Contains(entry.Key.ToString()))
209 + {
210 + ArrayList features = (ArrayList)featureComponents[entry.Key.ToString()];
211 + foreach (string featureId in features)
212 + {
213 + if (!featureOps.ContainsKey(featureId) || RowOperation.Delete != (RowOperation)featureOps[featureId])
214 + {
215 + // The feature was not deleted.
216 + this.OnMessage(WixErrors.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, this.transformPath));
217 + }
218 + }
219 + }
220 + }
221 + }
222 +
223 + // Warn if new components are added to existing features
224 + if (null != featureComponentsTable)
225 + {
226 + foreach (Row row in featureComponentsTable.Rows)
227 + {
228 + if (RowOperation.Add == row.Operation)
229 + {
230 + // Check if the feature is in the Feature table
231 + string feature_ = (string)row[0];
232 + string component_ = (string)row[1];
233 +
234 + // Features may not be present if not referenced
235 + if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_])
236 + {
237 + this.OnMessage(WixWarnings.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, this.transformPath));
238 + }
239 + }
240 + }
241 + }
242 +#endif
243 + throw new NotImplementedException();
244 + }
245 + }
246 +}
src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+4 -4
@@ -86,14 +86,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
86
87 // setup up the query record and find the appropriate file in the
88 // previously executed file view
89 - fileQueryRecord[1] = facade.File.Id.Id;
89 + fileQueryRecord[1] = facade.Id;
90 fileView.Execute(fileQueryRecord);
91
92 using (Record fileRecord = fileView.Fetch())
93 {
94 if (null == fileRecord)
95 {
96 - throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.Id.Id));
96 + throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id));
97 }
98
99 relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
@@ -102,7 +102,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
102 // finally put together the base media layout path and the relative file layout path
103 var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
104
105 - var transfer = this.BackendHelper.CreateFileTransfer(facade.File.Source.Path, fileLayoutPath, false, facade.File.SourceLineNumbers);
105 + var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber);
106 fileTransfers.Add(transfer);
107
108 // Track the location where the cabinet will be placed. If the transfer is
@@ -110,7 +110,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
110 // because if the source and destination of the transfer is the same, we
111 // don't want to clean the file because we'd be deleting the original
112 // (and that would be bad).
113 - var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.File.SourceLineNumbers);
113 + var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber);
114 tracked.Clean = !transfer.Redundant;
115
116 trackedFiles.Add(tracked);
src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
+7 -6
@@ -11,24 +11,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Extensibility.Services;
13
14 + /// <summary>
15 + /// Set sequence numbers for all the actions and create tuples in the output object.
16 + /// </summary>
17 internal class SequenceActionsCommand
18 {
16 - public SequenceActionsCommand(IntermediateSection section)
19 + public SequenceActionsCommand(IMessaging messaging, IntermediateSection section)
20 {
21 + this.Messaging = messaging;
22 this.Section = section;
23
24 this.RelativeActionsForActions = new Dictionary<string, RelativeActions>();
25 }
26
27 + private IMessaging Messaging { get; }
28 +
29 private IntermediateSection Section { get; }
30
31 private Dictionary<string, RelativeActions> RelativeActionsForActions { get; }
32
27 - public IMessaging Messaging { private get; set; }
28 -
29 - /// <summary>
30 - /// Set sequence numbers for all the actions and create tuples in the output object.
31 - /// </summary>
33 public void Execute()
34 {
35 var requiredActionTuples = new Dictionary<string, WixActionTuple>();
src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+51 -51
@@ -59,27 +59,27 @@ namespace WixToolset.Core.WindowsInstaller.Bind
59 FileInfo fileInfo = null;
60 try
61 {
62 - fileInfo = new FileInfo(facade.File.Source.Path);
62 + fileInfo = new FileInfo(facade.SourcePath);
63 }
64 catch (ArgumentException)
65 {
66 - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.File.SourceLineNumbers, facade.File.Source.Path));
66 + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
67 return;
68 }
69 catch (PathTooLongException)
70 {
71 - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.File.SourceLineNumbers, facade.File.Source.Path));
71 + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
72 return;
73 }
74 catch (NotSupportedException)
75 {
76 - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.File.SourceLineNumbers, facade.File.Source.Path));
76 + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
77 return;
78 }
79
80 if (!fileInfo.Exists)
81 {
82 - this.Messaging.Write(ErrorMessages.CannotFindFile(facade.File.SourceLineNumbers, facade.File.Id.Id, facade.File.Name, facade.File.Source.Path));
82 + this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath));
83 return;
84 }
85
@@ -87,10 +87,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
87 {
88 if (Int32.MaxValue < fileStream.Length)
89 {
90 - throw new WixException(ErrorMessages.FileTooLarge(facade.File.SourceLineNumbers, facade.File.Source.Path));
90 + throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath));
91 }
92
93 - facade.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
93 + facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
94 }
95
96 string version = null;
@@ -103,7 +103,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
103 {
104 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
105 {
106 - throw new WixException(ErrorMessages.FileNotFound(facade.File.SourceLineNumbers, fileInfo.FullName));
106 + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
107 }
108 else
109 {
@@ -118,7 +118,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
118 {
119 // not overwriting hash, so don't do the rest of these options.
120 }
121 - else if (null != facade.File.Version)
121 + else if (null != facade.Version)
122 {
123 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
124 // 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.
@@ -127,16 +127,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
127 //
128 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
129 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
130 - if (!this.FileFacades.Any(r => facade.File.Version.Equals(r.File.Id.Id, StringComparison.Ordinal)))
130 + if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal)))
131 {
132 - this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.File.SourceLineNumbers, facade.File.Version, facade.File.Id.Id));
132 + this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
133 }
134 }
135 else
136 {
137 - if (null != facade.File.Language)
137 + if (null != facade.Language)
138 {
139 - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.File.SourceLineNumbers, facade.File.Language, facade.File.Id.Id));
139 + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
140 }
141
142 int[] hash;
@@ -148,7 +148,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
148 {
149 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
150 {
151 - throw new WixException(ErrorMessages.FileNotFound(facade.File.SourceLineNumbers, fileInfo.FullName));
151 + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
152 }
153 else
154 {
@@ -158,7 +158,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
158
159 if (null == facade.Hash)
160 {
161 - facade.Hash = new MsiFileHashTuple(facade.File.SourceLineNumbers, facade.File.Id);
161 + facade.Hash = new MsiFileHashTuple(facade.SourceLineNumber, facade.Identifier);
162 this.Section.Tuples.Add(facade.Hash);
163 }
164
@@ -173,11 +173,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
173 {
174 // If no version was provided by the user, use the version from the file itself.
175 // This is the most common case.
176 - if (String.IsNullOrEmpty(facade.File.Version))
176 + if (String.IsNullOrEmpty(facade.Version))
177 {
178 - facade.File.Version = version;
178 + facade.Version = version;
179 }
180 - else if (!this.FileFacades.Any(r => facade.File.Version.Equals(r.File.Id.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
180 + else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
181 {
182 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
183 // the version value). We didn't find it so, we will override the default version they provided with the actual
@@ -188,41 +188,41 @@ namespace WixToolset.Core.WindowsInstaller.Bind
188 //
189 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
190 // That's typically even more rare than companion files so again, no index, just search.
191 - facade.File.Version = version;
191 + facade.Version = version;
192 }
193
194 - if (!String.IsNullOrEmpty(facade.File.Language) && String.IsNullOrEmpty(language))
194 + if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language))
195 {
196 - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.File.SourceLineNumbers, facade.File.Language, facade.File.Id.Id));
196 + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
197 }
198 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
199 {
200 - facade.File.Language = language;
200 + facade.Language = language;
201 }
202
203 // Populate the binder variables for this file information if requested.
204 if (null != this.VariableCache)
205 {
206 - if (!String.IsNullOrEmpty(facade.File.Version))
206 + if (!String.IsNullOrEmpty(facade.Version))
207 {
208 - var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.File.Id.Id);
209 - this.VariableCache[key] = facade.File.Version;
208 + var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id);
209 + this.VariableCache[key] = facade.Version;
210 }
211
212 - if (!String.IsNullOrEmpty(facade.File.Language))
212 + if (!String.IsNullOrEmpty(facade.Language))
213 {
214 - var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.File.Id.Id);
215 - this.VariableCache[key] = facade.File.Language;
214 + var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id);
215 + this.VariableCache[key] = facade.Language;
216 }
217 }
218 }
219
220 // If this is a CLR assembly, load the assembly and get the assembly name information
221 - if (AssemblyType.DotNetAssembly == facade.Assembly?.Type)
221 + if (AssemblyType.DotNetAssembly == facade.AssemblyType)
222 {
223 try
224 {
225 - var assemblyName = AssemblyNameReader.ReadAssembly(facade.File.SourceLineNumbers, fileInfo.FullName, version);
225 + var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version);
226
227 this.SetMsiAssemblyName(assemblyNameTuples, facade, "name", assemblyName.Name);
228 this.SetMsiAssemblyName(assemblyNameTuples, facade, "culture", assemblyName.Culture);
@@ -242,9 +242,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
242 {
243 this.SetMsiAssemblyName(assemblyNameTuples, facade, "publicKeyToken", assemblyName.PublicKeyToken);
244 }
245 - else if (facade.Assembly.ApplicationFileRef == null)
245 + else if (facade.AssemblyApplicationFileRef == null)
246 {
247 - throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.File.SourceLineNumbers, fileInfo.FullName, facade.File.ComponentRef));
247 + throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef));
248 }
249
250 if (!String.IsNullOrEmpty(assemblyName.FileVersion))
@@ -255,7 +255,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
255 // add the assembly name to the information cache
256 if (null != this.VariableCache)
257 {
258 - this.VariableCache[$"assemblyfullname.{facade.File.Id.Id}"] = assemblyName.GetFullName();
258 + this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName();
259 }
260 }
261 catch (WixException e)
@@ -263,20 +263,20 @@ namespace WixToolset.Core.WindowsInstaller.Bind
263 this.Messaging.Write(e.Error);
264 }
265 }
266 - else if (AssemblyType.Win32Assembly == facade.Assembly?.Type)
266 + else if (AssemblyType.Win32Assembly == facade.AssemblyType)
267 {
268 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
269 // all files like this. Even though this is a rare case it looks like we might be able to index the
270 // file earlier.
271 - var fileManifest = this.FileFacades.FirstOrDefault(r => r.File.Id.Id.Equals(facade.Assembly.ManifestFileRef, StringComparison.Ordinal));
271 + var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal));
272 if (null == fileManifest)
273 {
274 - this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.File.SourceLineNumbers, facade.File.Id.Id, facade.Assembly.ManifestFileRef));
274 + this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef));
275 }
276
277 try
278 {
279 - var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.File.SourceLineNumbers, fileManifest.File.Source.Path);
279 + var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath);
280
281 if (!String.IsNullOrEmpty(assemblyName.Name))
282 {
@@ -315,41 +315,41 @@ namespace WixToolset.Core.WindowsInstaller.Bind
315 /// create a new row.
316 /// </summary>
317 /// <param name="assemblyNameTuples">MsiAssemblyName table.</param>
318 - /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
318 + /// <param name="facade">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
319 /// <param name="name">MsiAssemblyName name.</param>
320 /// <param name="value">MsiAssemblyName value.</param>
321 - private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameTuple> assemblyNameTuples, FileFacade file, string name, string value)
321 + private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameTuple> assemblyNameTuples, FileFacade facade, string name, string value)
322 {
323 // check for null value (this can occur when grabbing the file version from an assembly without one)
324 if (String.IsNullOrEmpty(value))
325 {
326 - this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.ComponentRef, name));
326 + this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name));
327 }
328 else
329 {
330 // 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.
331 - if ("name" == name && AssemblyType.DotNetAssembly == file.Assembly.Type &&
332 - String.IsNullOrEmpty(file.Assembly.ApplicationFileRef) &&
333 - !String.Equals(Path.GetFileNameWithoutExtension(file.File.Name), value, StringComparison.OrdinalIgnoreCase))
331 + if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType &&
332 + String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) &&
333 + !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase))
334 {
335 - this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.Name), value));
335 + this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
336 }
337
338 // override directly authored value
339 - var lookup = String.Concat(file.File.ComponentRef, "/", name);
339 + var lookup = String.Concat(facade.ComponentRef, "/", name);
340 if (!assemblyNameTuples.TryGetValue(lookup, out var assemblyNameRow))
341 {
342 - assemblyNameRow = new MsiAssemblyNameTuple(file.File.SourceLineNumbers);
343 - assemblyNameRow.ComponentRef = file.File.ComponentRef;
342 + assemblyNameRow = new MsiAssemblyNameTuple(facade.SourceLineNumber);
343 + assemblyNameRow.ComponentRef = facade.ComponentRef;
344 assemblyNameRow.Name = name;
345 assemblyNameRow.Value = value;
346
347 - if (null == file.AssemblyNames)
347 + if (null == facade.AssemblyNames)
348 {
349 - file.AssemblyNames = new List<MsiAssemblyNameTuple>();
349 + facade.AssemblyNames = new List<MsiAssemblyNameTuple>();
350 }
351
352 - file.AssemblyNames.Add(assemblyNameRow);
352 + facade.AssemblyNames.Add(assemblyNameRow);
353 this.Section.Tuples.Add(assemblyNameRow);
354 }
355
@@ -357,7 +357,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
357
358 if (this.VariableCache != null)
359 {
360 - var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, file.File.Id.Id).ToLowerInvariant();
360 + var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant();
361 this.VariableCache[key] = value;
362 }
363 }
src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+10 -10
@@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
33
34 // Order by Component to group the files by directory.
35 var optimized = this.OptimizedFileFacades();
36 - foreach (var fileId in optimized.Select(f => f.File.Id.Id))
36 + foreach (var fileId in optimized.Select(f => f.Id))
37 {
38 var fileRow = fileRows.Get(fileId);
39 fileRow.Sequence = ++lastSequence;
@@ -41,13 +41,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
41 }
42 else
43 {
44 - int lastSequence = 0;
44 + var lastSequence = 0;
45 MediaRow mediaRow = null;
46 - Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>();
46 + var patchGroups = new Dictionary<int, List<FileFacade>>();
47
48 // sequence the non-patch-added files
49 var optimized = this.OptimizedFileFacades();
50 - foreach (FileFacade facade in optimized)
50 + foreach (var facade in optimized)
51 {
52 if (null == mediaRow)
53 {
@@ -64,19 +64,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
64 mediaRow = mediaRows.Get(facade.DiskId);
65 }
66
67 - if (facade.File.PatchGroup.HasValue)
67 + if (facade.PatchGroup.HasValue)
68 {
69 - if (patchGroups.TryGetValue(facade.File.PatchGroup.Value, out var patchGroup))
69 + if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup))
70 {
71 patchGroup = new List<FileFacade>();
72 - patchGroups.Add(facade.File.PatchGroup.Value, patchGroup);
72 + patchGroups.Add(facade.PatchGroup.Value, patchGroup);
73 }
74
75 patchGroup.Add(facade);
76 }
77 else
78 {
79 - var fileRow = fileRows.Get(facade.File.Id.Id);
79 + var fileRow = fileRows.Get(facade.Id);
80 fileRow.Sequence = ++lastSequence;
81 }
82 }
@@ -102,7 +102,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
102 mediaRow = mediaRows.Get(facade.DiskId);
103 }
104
105 - var fileRow = fileRows.Get(facade.File.Id.Id);
105 + var fileRow = fileRows.Get(facade.Id);
106 fileRow.Sequence = ++lastSequence;
107 }
108 }
@@ -119,7 +119,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
119 // TODO: Sort these facades even smarter by directory path and component id
120 // and maybe file size or file extension and other creative ideas to
121 // get optimal install speed out of MSI.
122 - return this.FileFacades.OrderBy(f => f.File.ComponentRef);
122 + return this.FileFacades.OrderBy(f => f.ComponentRef);
123 }
124 }
125 }
src/WixToolset.Core.WindowsInstaller/Data/tables.xml
+4
@@ -156,6 +156,10 @@
156 minValue="0" maxValue="32767" description="Integer containing bit flags representing file attributes (with the decimal value of each bit position in parentheses)"/>
157 <columnDefinition name="Sequence" type="number" length="4"
158 minValue="1" maxValue="2147483647" description="Sequence with respect to the media images; order must track cabinet order."/>
159 + <columnDefinition name="DiskId" type="number" length="4" unreal="yes"
160 + minValue="1" maxValue="32767" description="Disk identifier for the file."/>
161 + <columnDefinition name="Source" type="object" length="0" unreal="yes"
162 + category="binary" description="Path to source of file."/>
163 </tableDefinition>
164 <tableDefinition name="CCPSearch">
165 <columnDefinition name="Signature_" type="string" length="72" primaryKey="yes"
src/WixToolset.Core.WindowsInstaller/Differ.cs
+4
@@ -1,5 +1,7 @@
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 +#if DELETE
4 +
5 namespace WixToolset.Core.WindowsInstaller
6 {
7 using System;
@@ -604,3 +606,5 @@ namespace WixToolset.Core.WindowsInstaller
606 }
607 }
608 }
609 +
610 +#endif
src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
+40 -40
@@ -33,40 +33,40 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
33 public bool Execute()
34 {
35 // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered
36 - bool foundUnsignedExternals = false;
37 - bool shouldCommit = false;
36 + var foundUnsignedExternals = false;
37 + var shouldCommit = false;
38
39 - FileAttributes attributes = File.GetAttributes(this.Context.InputFilePath);
39 + var attributes = File.GetAttributes(this.Context.InputFilePath);
40 if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly))
41 {
42 this.Messaging.Write(ErrorMessages.ReadOnlyOutputFile(this.Context.InputFilePath));
43 return shouldCommit;
44 }
45
46 - using (Database database = new Database(this.Context.InputFilePath, OpenDatabase.Transact))
46 + using (var database = new Database(this.Context.InputFilePath, OpenDatabase.Transact))
47 {
48 // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content
49 - int codepage = 1252;
49 + var codepage = 1252;
50
51 // list of certificates for this database (hash/identifier)
52 - Dictionary<string, string> certificates = new Dictionary<string, string>();
52 + var certificates = new Dictionary<string, string>();
53
54 // Reset the in-memory tables for this new database
55 - Table digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]);
56 - Table digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]);
55 + var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]);
56 + var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]);
57
58 // If any digital signature records exist that are not of the media type, preserve them
59 if (database.TableExists("MsiDigitalSignature"))
60 {
61 - using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'"))
61 + using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'"))
62 {
63 - foreach (Record digitalSignatureRecord in digitalSignatureView.Records)
63 + foreach (var digitalSignatureRecord in digitalSignatureView.Records)
64 {
65 Row digitalSignatureRow = null;
66 digitalSignatureRow = digitalSignatureTable.CreateRow(null);
67
68 - string table = digitalSignatureRecord.GetString(0);
69 - string signObject = digitalSignatureRecord.GetString(1);
68 + var table = digitalSignatureRecord.GetString(0);
69 + var signObject = digitalSignatureRecord.GetString(1);
70
71 digitalSignatureRow[0] = table;
72 digitalSignatureRow[1] = signObject;
@@ -75,16 +75,16 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
75 if (false == digitalSignatureRecord.IsNull(3))
76 {
77 // Export to a file, because the MSI API's require us to provide a file path on disk
78 - string hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature");
79 - string hashFileName = string.Concat(table, ".", signObject, ".bin");
78 + var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature");
79 + var hashFileName = String.Concat(table, ".", signObject, ".bin");
80
81 Directory.CreateDirectory(hashPath);
82 hashPath = Path.Combine(hashPath, hashFileName);
83
84 - using (FileStream fs = File.Create(hashPath))
84 + using (var fs = File.Create(hashPath))
85 {
86 int bytesRead;
87 - byte[] buffer = new byte[1024 * 4];
87 + var buffer = new byte[1024 * 4];
88
89 while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length)))
90 {
@@ -101,21 +101,21 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
101 // If any digital certificates exist, extract and preserve them
102 if (database.TableExists("MsiDigitalCertificate"))
103 {
104 - using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`"))
104 + using (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`"))
105 {
106 - foreach (Record digitalCertificateRecord in digitalCertificateView.Records)
106 + foreach (var digitalCertificateRecord in digitalCertificateView.Records)
107 {
108 - string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate
108 + var certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate
109
110 // Export to a file, because the MSI API's require us to provide a file path on disk
111 - string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
111 + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
112 Directory.CreateDirectory(certPath);
113 - certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer"));
113 + certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer"));
114
115 - using (FileStream fs = File.Create(certPath))
115 + using (var fs = File.Create(certPath))
116 {
117 int bytesRead;
118 - byte[] buffer = new byte[1024 * 4];
118 + var buffer = new byte[1024 * 4];
119
120 while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length)))
121 {
@@ -124,37 +124,37 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
124 }
125
126 // Add it to our "add to MsiDigitalCertificate" table dictionary
127 - Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
127 + var digitalCertificateRow = digitalCertificateTable.CreateRow(null);
128 digitalCertificateRow[0] = certificateId;
129
130 // Now set the file path on disk where this binary stream will be picked up at import time
131 - digitalCertificateRow[1] = string.Concat(certificateId, ".cer");
131 + digitalCertificateRow[1] = String.Concat(certificateId, ".cer");
132
133 // Load the cert to get it's thumbprint
134 - X509Certificate cert = X509Certificate.CreateFromCertFile(certPath);
135 - X509Certificate2 cert2 = new X509Certificate2(cert);
134 + var cert = X509Certificate.CreateFromCertFile(certPath);
135 + var cert2 = new X509Certificate2(cert);
136
137 certificates.Add(cert2.Thumbprint, certificateId);
138 }
139 }
140 }
141
142 - using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`"))
142 + using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`"))
143 {
144 - foreach (Record mediaRecord in mediaView.Records)
144 + foreach (var mediaRecord in mediaView.Records)
145 {
146 X509Certificate2 cert2 = null;
147 Row digitalSignatureRow = null;
148
149 - string cabName = mediaRecord.GetString(4); // get the name of the cab
149 + var cabName = mediaRecord.GetString(4); // get the name of the cab
150 // If there is no cabinet or it's an internal cab, skip it.
151 if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal))
152 {
153 continue;
154 }
155
156 - string cabId = mediaRecord.GetString(1); // get the ID of the cab
157 - string cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName);
156 + var cabId = mediaRecord.GetString(1); // get the ID of the cab
157 + var cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName);
158
159 // If the cabs aren't there, throw an error but continue to catch the other errors
160 if (!File.Exists(cabPath))
@@ -166,12 +166,12 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
166 try
167 {
168 // Get the certificate from the cab
169 - X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath);
169 + var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath);
170 cert2 = new X509Certificate2(signedFileCert);
171 }
172 catch (System.Security.Cryptography.CryptographicException e)
173 {
174 - uint HResult = unchecked((uint)Marshal.GetHRForException(e));
174 + var HResult = unchecked((uint)Marshal.GetHRForException(e));
175
176 // If the file has no cert, continue, but flag that we found at least one so we can later give a warning
177 if (0x80092009 == HResult) // CRYPT_E_NO_MATCH
@@ -197,26 +197,26 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
197 if (!certificates.ContainsKey(cert2.Thumbprint))
198 {
199 // generate a stable identifier
200 - string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint);
200 + var certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint);
201
202 // Add it to our "add to MsiDigitalCertificate" table dictionary
203 - Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
203 + var digitalCertificateRow = digitalCertificateTable.CreateRow(null);
204 digitalCertificateRow[0] = certificateGeneratedId;
205
206 // Export to a file, because the MSI API's require us to provide a file path on disk
207 - string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
207 + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
208 Directory.CreateDirectory(certPath);
209 - certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer"));
209 + certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer"));
210 File.Delete(certPath);
211
212 - using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create)))
212 + using (var writer = new BinaryWriter(File.Open(certPath, FileMode.Create)))
213 {
214 writer.Write(cert2.RawData);
215 writer.Close();
216 }
217
218 // Now set the file path on disk where this binary stream will be picked up at import time
219 - digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer");
219 + digitalCertificateRow[1] = String.Concat(cert2.Thumbprint, ".cer");
220
221 certificates.Add(cert2.Thumbprint, certificateGeneratedId);
222 }
src/WixToolset.Core.WindowsInstaller/MspBackend.cs
+53 -15
@@ -3,36 +3,74 @@
3 namespace WixToolset.Core.WindowsInstaller
4 {
5 using System;
6 - using System.ComponentModel;
6 + using System.Collections.Generic;
7 using System.IO;
8 - using WixToolset.Core.Native;
8 + using System.Linq;
9 + using WixToolset.Core.WindowsInstaller.Bind;
10 + using WixToolset.Core.WindowsInstaller.Msi;
11 using WixToolset.Core.WindowsInstaller.Unbind;
12 using WixToolset.Data;
11 - using WixToolset.Data.Bind;
13 + using WixToolset.Data.Tuples;
14 + using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Extensibility;
16 using WixToolset.Extensibility.Data;
14 - using WixToolset.Ole32;
17 + using WixToolset.Extensibility.Services;
18
19 internal class MspBackend : IBackend
20 {
21 public IBindResult Bind(IBindContext context)
22 {
20 - throw new NotImplementedException();
21 - }
23 + var messaging = context.ServiceProvider.GetService<IMessaging>();
24
23 - public IDecompileResult Decompile(IDecompileContext context)
24 - {
25 - throw new NotImplementedException();
26 - }
25 + var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
26
28 - public bool Inscribe(IInscribeContext context)
29 - {
30 - throw new NotImplementedException();
27 + var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>();
28 +
29 + foreach (var extension in backendExtensions)
30 + {
31 + extension.PreBackendBind(context);
32 + }
33 +
34 + // Create transforms named in patch transforms.
35 + IEnumerable<PatchTransform> patchTransforms;
36 + {
37 + var command = new CreatePatchTransformsCommand(messaging, context.IntermediateRepresentation, context.IntermediateFolder);
38 + patchTransforms = command.Execute();
39 + }
40 +
41 + // Enhance the intermediate by attaching the created patch transforms.
42 + IEnumerable<SubStorage> subStorages;
43 + {
44 + var command = new AttachPatchTransformsCommand(messaging, context.IntermediateRepresentation, patchTransforms);
45 + subStorages = command.Execute();
46 + }
47 +
48 + // Create WindowsInstallerData with patch metdata and transforms as sub-storages
49 + // Create MSP from WindowsInstallerData
50 + using (var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null))
51 + {
52 + command.Execute();
53 +
54 + var result = context.ServiceProvider.GetService<IBindResult>();
55 + result.FileTransfers = command.FileTransfers;
56 + result.TrackedFiles = command.TrackedFiles;
57 +
58 + foreach (var extension in backendExtensions)
59 + {
60 + extension.PostBackendBind(result, command.Wixout);
61 + }
62 +
63 + return result;
64 + }
65 }
66
67 + public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException();
68 +
69 + public bool Inscribe(IInscribeContext context) => throw new NotImplementedException();
70 +
71 public Intermediate Unbind(IUnbindContext context)
72 {
35 -#if REVISIT_FOR_PATCHING
73 +#if TODO_PATCHING
74 Output patch;
75
76 // patch files are essentially database files (use a special flag to let the API know its a patch file)
@@ -116,4 +154,4 @@ namespace WixToolset.Core.WindowsInstaller
154 throw new NotImplementedException();
155 }
156 }
119 -}
\ No newline at end of file
157 +}
src/WixToolset.Core.WindowsInstaller/MstBackend.cs
+1 -1
@@ -12,7 +12,7 @@ namespace WixToolset.Core.WindowsInstaller
12 {
13 public IBindResult Bind(IBindContext context)
14 {
15 -#if REVISIT_FOR_PATCHING
15 +#if TODO_PATCHING
16 var command = new BindTransformCommand();
17 command.Extensions = context.Extensions;
18 command.TempFilesLocation = context.IntermediateFolder;
src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs
+1 -1
@@ -63,8 +63,8 @@ namespace WixToolset.Ole32
63 /// </summary>
64 internal sealed class Storage : IDisposable
65 {
66 + private readonly IStorage storage;
67 private bool disposed;
67 - private IStorage storage;
68
69 /// <summary>
70 /// Instantiate a new Storage.
src/WixToolset.Core.WindowsInstaller/Patch.cs
+5 -2
@@ -1,5 +1,7 @@
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 +#if DELETE
4 +
5 namespace WixToolset.Data
6 {
7 using System;
@@ -50,7 +52,6 @@ namespace WixToolset.Data
52 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
53 public void AttachTransforms(List<PatchTransform> transforms)
54 {
53 -#if REVISIT_FOR_PATCHING
55 // Track if at least one transform gets attached.
56 bool attachedTransform = false;
57
@@ -1229,8 +1230,10 @@ namespace WixToolset.Data
1230 }
1231
1232 return pairedTransform;
1232 -#endif
1233 +
1234 throw new NotImplementedException();
1235 }
1236 }
1237 }
1238 +
1239 +#endif
src/WixToolset.Core.WindowsInstaller/PatchTransform.cs
+5 -2
@@ -1,5 +1,7 @@
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 +#if DELETE
4 +
5 namespace WixToolset
6 {
7 using System;
@@ -50,7 +52,6 @@ namespace WixToolset
52 /// </summary>
53 public void Validate()
54 {
53 -#if REVISIT_FOR_PATCHING
55 // Changing the ProdocutCode in a patch transform is not recommended.
56 Table propertyTable = this.Transform.Tables["Property"];
57 if (null != propertyTable)
@@ -261,8 +262,10 @@ namespace WixToolset
262 }
263 }
264 }
264 -#endif
265 +
266 throw new NotImplementedException();
267 }
268 }
269 }
270 +
271 +#endif
\ No newline at end of file
src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs
+1 -1
@@ -19,7 +19,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
19
20 public Intermediate Execute()
21 {
22 -#if REVISIT_FOR_PATCHING
22 +#if TODO_PATCHING
23 Output output;
24
25 try
src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
+58 -67
@@ -4,6 +4,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
4 {
5 using System;
6 using System.Collections;
7 + using System.Collections.Generic;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
@@ -12,7 +13,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
13 using WixToolset.Core.WindowsInstaller.Msi;
14 using WixToolset.Data;
15 using WixToolset.Data.WindowsInstaller;
15 - using WixToolset.Extensibility;
16 using WixToolset.Extensibility.Services;
17
18 internal class UnbindTransformCommand
@@ -41,21 +41,21 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
41
42 public WindowsInstallerData Execute()
43 {
44 - WindowsInstallerData transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile));
44 + var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile));
45 transform.Type = OutputType.Transform;
46
47 // get the summary information table
48 - using (SummaryInformation summaryInformation = new SummaryInformation(this.TransformFile))
48 + using (var summaryInformation = new SummaryInformation(this.TransformFile))
49 {
50 - Table table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
50 + var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
51
52 - for (int i = 1; 19 >= i; i++)
52 + for (var i = 1; 19 >= i; i++)
53 {
54 - string value = summaryInformation.GetProperty(i);
54 + var value = summaryInformation.GetProperty(i);
55
56 if (0 < value.Length)
57 {
58 - Row row = table.CreateRow(transform.SourceLineNumbers);
58 + var row = table.CreateRow(transform.SourceLineNumbers);
59 row[0] = i;
60 row[1] = value;
61 }
@@ -63,9 +63,9 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
63 }
64
65 // create a schema msi which hopefully matches the table schemas in the transform
66 - WindowsInstallerData schemaOutput = new WindowsInstallerData(null);
67 - string msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi");
68 - foreach (TableDefinition tableDefinition in this.TableDefinitions)
66 + var schemaOutput = new WindowsInstallerData(null);
67 + var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi");
68 + foreach (var tableDefinition in this.TableDefinitions)
69 {
70 // skip unreal tables and the Patch table
71 if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name)
@@ -74,40 +74,40 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
74 }
75 }
76
77 - Hashtable addedRows = new Hashtable();
77 + var addedRows = new Dictionary<string, Row>();
78 Table transformViewTable;
79
80 // Bind the schema msi.
81 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
82
83 // apply the transform to the database and retrieve the modifications
84 - using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
84 + using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
85 {
86 // apply the transform with the ViewTransform option to collect all the modifications
87 msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform);
88
89 // unbind the database
90 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true);
91 - WindowsInstallerData transformViewOutput = unbindCommand.Execute();
91 + var transformViewOutput = unbindCommand.Execute();
92
93 // index the added and possibly modified rows (added rows may also appears as modified rows)
94 transformViewTable = transformViewOutput.Tables["_TransformView"];
95 - Hashtable modifiedRows = new Hashtable();
96 - foreach (Row row in transformViewTable.Rows)
95 + var modifiedRows = new Hashtable();
96 + foreach (var row in transformViewTable.Rows)
97 {
98 - string tableName = (string)row[0];
99 - string columnName = (string)row[1];
100 - string primaryKeys = (string)row[2];
98 + var tableName = (string)row[0];
99 + var columnName = (string)row[1];
100 + var primaryKeys = (string)row[2];
101
102 if ("INSERT" == columnName)
103 {
104 - string index = String.Concat(tableName, ':', primaryKeys);
104 + var index = String.Concat(tableName, ':', primaryKeys);
105
106 addedRows.Add(index, null);
107 }
108 else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row
109 {
110 - string index = String.Concat(tableName, ':', primaryKeys);
110 + var index = String.Concat(tableName, ':', primaryKeys);
111
112 modifiedRows[index] = row;
113 }
@@ -116,16 +116,16 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
116 // create placeholder rows for modified rows to make the transform insert the updated values when its applied
117 foreach (Row row in modifiedRows.Values)
118 {
119 - string tableName = (string)row[0];
120 - string columnName = (string)row[1];
121 - string primaryKeys = (string)row[2];
119 + var tableName = (string)row[0];
120 + var columnName = (string)row[1];
121 + var primaryKeys = (string)row[2];
122
123 - string index = String.Concat(tableName, ':', primaryKeys);
123 + var index = String.Concat(tableName, ':', primaryKeys);
124
125 // ignore information for added rows
126 - if (!addedRows.Contains(index))
126 + if (!addedRows.ContainsKey(index))
127 {
128 - Table table = schemaOutput.Tables[tableName];
128 + var table = schemaOutput.Tables[tableName];
129 this.CreateRow(table, primaryKeys, true);
130 }
131 }
@@ -135,7 +135,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
135 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
136
137 // apply the transform to the database and retrieve the modifications
138 - using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
138 + using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
139 {
140 try
141 {
@@ -158,26 +158,26 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
158
159 // unbind the database
160 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true);
161 - WindowsInstallerData output = unbindCommand.Execute();
161 + var output = unbindCommand.Execute();
162
163 // index all the rows to easily find modified rows
164 - Hashtable rows = new Hashtable();
165 - foreach (Table table in output.Tables)
164 + var rows = new Dictionary<string, Row>();
165 + foreach (var table in output.Tables)
166 {
167 - foreach (Row row in table.Rows)
167 + foreach (var row in table.Rows)
168 {
169 rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row);
170 }
171 }
172
173 // process the _TransformView rows into transform rows
174 - foreach (Row row in transformViewTable.Rows)
174 + foreach (var row in transformViewTable.Rows)
175 {
176 - string tableName = (string)row[0];
177 - string columnName = (string)row[1];
178 - string primaryKeys = (string)row[2];
176 + var tableName = (string)row[0];
177 + var columnName = (string)row[1];
178 + var primaryKeys = (string)row[2];
179
180 - Table table = transform.EnsureTable(this.TableDefinitions[tableName]);
180 + var table = transform.EnsureTable(this.TableDefinitions[tableName]);
181
182 if ("CREATE" == columnName) // added table
183 {
@@ -185,7 +185,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
185 }
186 else if ("DELETE" == columnName) // deleted row
187 {
188 - Row deletedRow = this.CreateRow(table, primaryKeys, false);
188 + var deletedRow = this.CreateRow(table, primaryKeys, false);
189 deletedRow.Operation = RowOperation.Delete;
190 }
191 else if ("DROP" == columnName) // dropped table
@@ -194,24 +194,24 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
194 }
195 else if ("INSERT" == columnName) // added row
196 {
197 - string index = String.Concat(tableName, ':', primaryKeys);
198 - Row addedRow = (Row)rows[index];
197 + var index = String.Concat(tableName, ':', primaryKeys);
198 + var addedRow = rows[index];
199 addedRow.Operation = RowOperation.Add;
200 table.Rows.Add(addedRow);
201 }
202 else if (null != primaryKeys) // modified row
203 {
204 - string index = String.Concat(tableName, ':', primaryKeys);
204 + var index = String.Concat(tableName, ':', primaryKeys);
205
206 // the _TransformView table includes information for added rows
207 // that looks like modified rows so it sometimes needs to be ignored
208 - if (!addedRows.Contains(index))
208 + if (!addedRows.ContainsKey(index))
209 {
210 - Row modifiedRow = (Row)rows[index];
210 + var modifiedRow = rows[index];
211
212 // mark the field as modified
213 - int indexOfModifiedValue = -1;
214 - for (int i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i)
213 + var indexOfModifiedValue = -1;
214 + for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i)
215 {
216 if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal))
217 {
@@ -231,7 +231,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
231 }
232 else // added column
233 {
234 - ColumnDefinition column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal));
234 + var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal));
235 column.Added = true;
236 }
237 }
@@ -240,21 +240,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
240 return transform;
241 }
242
243 - private void GenerateDatabase(WindowsInstallerData output, string databaseFile)
244 - {
245 - var command = new GenerateDatabaseCommand();
246 - command.Extensions = Array.Empty<IFileSystemExtension>();
247 - command.Output = output;
248 - command.OutputPath = databaseFile;
249 - command.KeepAddedColumns = true;
250 - command.UseSubDirectory = false;
251 - command.SuppressAddingValidationRows = true;
252 - command.TableDefinitions = this.TableDefinitions;
253 - command.IntermediateFolder = this.IntermediateFolder;
254 - command.Codepage = -1;
255 - command.Execute();
256 - }
257 -
243 /// <summary>
244 /// Create a deleted or modified row.
245 /// </summary>
@@ -264,14 +249,14 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
249 /// <returns>The new row.</returns>
250 private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields)
251 {
267 - Row row = table.CreateRow(null);
252 + var row = table.CreateRow(null);
253
269 - string[] primaryKeyParts = primaryKeys.Split('\t');
270 - int primaryKeyPartIndex = 0;
254 + var primaryKeyParts = primaryKeys.Split('\t');
255 + var primaryKeyPartIndex = 0;
256
272 - for (int i = 0; i < table.Definition.Columns.Length; i++)
257 + for (var i = 0; i < table.Definition.Columns.Length; i++)
258 {
274 - ColumnDefinition columnDefinition = table.Definition.Columns[i];
259 + var columnDefinition = table.Definition.Columns[i];
260
261 if (columnDefinition.PrimaryKey)
262 {
@@ -294,8 +279,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
279 {
280 if (null == this.EmptyFile)
281 {
297 - this.EmptyFile = Path.GetTempFileName() + ".empty";
298 - using (FileStream fileStream = File.Create(this.EmptyFile))
282 + this.EmptyFile = Path.Combine(this.IntermediateFolder, ".empty");
283 + using (var fileStream = File.Create(this.EmptyFile))
284 {
285 }
286 }
@@ -311,5 +296,11 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
296
297 return row;
298 }
299 +
300 + private void GenerateDatabase(WindowsInstallerData output, string databaseFile)
301 + {
302 + var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false);
303 + command.Execute();
304 + }
305 }
306 }
src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs
+2 -3
@@ -1,11 +1,10 @@
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.
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.WindowsInstaller
4 {
5 using System;
6 using System.IO;
7 using WixToolset.Extensibility;
8 - using WixToolset.Extensibility.Data;
8
9 internal class WindowsInstallerBackendFactory : IBackendFactory
10 {
@@ -16,7 +15,7 @@ namespace WixToolset.Core.WindowsInstaller
15 outputType = Path.GetExtension(outputFile);
16 }
17
19 - switch (outputType.ToLowerInvariant())
18 + switch (outputType?.ToLowerInvariant())
19 {
20 case "module":
21 case ".msm":
src/WixToolset.Core/Bind/FileFacade.cs
+121 -7
@@ -2,32 +2,146 @@
2
3 namespace WixToolset.Core.Bind
4 {
5 + using System;
6 using System.Collections.Generic;
7 + using WixToolset.Data;
8 using WixToolset.Data.Tuples;
9 + using WixToolset.Data.WindowsInstaller;
10 + using WixToolset.Data.WindowsInstaller.Rows;
11
12 public class FileFacade
13 {
14 public FileFacade(FileTuple file, AssemblyTuple assembly)
15 {
12 - this.File = file;
13 - this.Assembly = assembly;
16 + this.FileTuple = file;
17 + this.AssemblyTuple = assembly;
18 }
19
20 public FileFacade(bool fromModule, FileTuple file)
21 {
22 this.FromModule = fromModule;
19 - this.File = file;
23 + this.FileTuple = file;
24 + }
25 +
26 + internal FileFacade(FileRow row)
27 + {
28 + this.FromTransform = true;
29 + this.FileRow = row;
30 }
31
32 public bool FromModule { get; }
33
24 - public FileTuple File { get; }
34 + public bool FromTransform { get; }
35 +
36 + private FileRow FileRow { get; }
37 +
38 + private FileTuple FileTuple { get; }
39 +
40 + private AssemblyTuple AssemblyTuple { get; }
41 +
42 + public string Id => this.FileRow == null ? this.FileTuple.Id.Id : this.FileRow.File;
43 +
44 + public Identifier Identifier => this.FileRow == null ? this.FileTuple.Id : throw new NotImplementedException();
45 +
46 + public string ComponentRef => this.FileRow == null ? this.FileTuple.ComponentRef : this.FileRow.Component;
47 +
48 + public int DiskId
49 + {
50 + get => this.FileRow == null ? this.FileTuple.DiskId ?? 0 : this.FileRow.DiskId;
51 + set
52 + {
53 + if (this.FileRow == null)
54 + {
55 + this.FileTuple.DiskId = value;
56 + }
57 + else
58 + {
59 + this.FileRow.DiskId = value;
60 + }
61 + }
62 + }
63 +
64 + public string FileName => this.FileRow == null ? this.FileTuple.Name : this.FileRow.FileName;
65 +
66 + public int FileSize
67 + {
68 + get => this.FileRow == null ? this.FileTuple.FileSize : this.FileRow.FileSize;
69 + set
70 + {
71 + if (this.FileRow == null)
72 + {
73 + this.FileTuple.FileSize = value;
74 + }
75 + else
76 + {
77 + this.FileRow.FileSize = value;
78 + }
79 + }
80 + }
81 +
82 + public string Language
83 + {
84 + get => this.FileRow == null ? this.FileTuple.Language : this.FileRow.Language;
85 + set
86 + {
87 + if (this.FileRow == null)
88 + {
89 + this.FileTuple.Language = value;
90 + }
91 + else
92 + {
93 + this.FileRow.Language = value;
94 + }
95 + }
96 + }
97 +
98 + public int? PatchGroup => this.FileRow == null ? this.FileTuple.PatchGroup : null;
99 +
100 + public int Sequence
101 + {
102 + get => this.FileRow == null ? this.FileTuple.Sequence : this.FileRow.Sequence;
103 + set
104 + {
105 + if (this.FileRow == null)
106 + {
107 + this.FileTuple.Sequence = value;
108 + }
109 + else
110 + {
111 + this.FileRow.Sequence = value;
112 + }
113 + }
114 + }
115 +
116 + public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileTuple.SourceLineNumbers : this.FileRow.SourceLineNumbers;
117 +
118 + public string SourcePath => this.FileRow == null ? this.FileTuple.Source.Path : this.FileRow.Source;
119 +
120 + public bool Compressed => this.FileRow == null ? (this.FileTuple.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed;
121 +
122 + public bool Uncompressed => this.FileRow == null ? (this.FileTuple.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
123 +
124 + public string Version
125 + {
126 + get => this.FileRow == null ? this.FileTuple.Version : this.FileRow.Version;
127 + set
128 + {
129 + if (this.FileRow == null)
130 + {
131 + this.FileTuple.Version = value;
132 + }
133 + else
134 + {
135 + this.FileRow.Version = value;
136 + }
137 + }
138 + }
139
26 - public AssemblyTuple Assembly { get; }
140 + public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : throw new NotImplementedException();
141
28 - public int DiskId => this.File.DiskId ?? 0;
142 + public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblyTuple?.ApplicationFileRef : throw new NotImplementedException();
143
30 - public bool Uncompressed => (this.File.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed;
144 + public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblyTuple?.ManifestFileRef : throw new NotImplementedException();
145
146 /// <summary>
147 /// Gets the set of MsiAssemblyName rows created for this file.
src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+4 -4
@@ -89,7 +89,7 @@ namespace WixToolset.Core.Bind
89 {
90 var objectField = field.AsPath();
91
92 -#if REVISIT_FOR_PATCHING
92 +#if TODO_PATCHING
93 // Skip file resolution if the file is to be deleted.
94 if (RowOperation.Delete == tuple.Operation)
95 {
@@ -111,7 +111,7 @@ namespace WixToolset.Core.Bind
111 {
112 if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe)
113 {
114 -#if REVISIT_FOR_PATCHING
114 +#if TODO_PATCHING
115 // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file
116 if (null == objectField.UnresolvedData)
117 {
@@ -129,7 +129,7 @@ namespace WixToolset.Core.Bind
129 var value = fileResolver.ResolveFile(objectField.Path, tuple.Definition, tuple.SourceLineNumbers, BindStage.Normal);
130 field.Set(value);
131 }
132 -#if REVISIT_FOR_PATCHING
132 +#if TODO_PATCHING
133 else // Re-base binding path scenario caused by pyro.exe -bt -bu
134 {
135 // by default, use the resolved Data for file lookup
@@ -158,7 +158,7 @@ namespace WixToolset.Core.Bind
158 }
159 }
160
161 -#if REVISIT_FOR_PATCHING
161 +#if TODO_PATCHING
162 if (null != objectField.PreviousData)
163 {
164 objectField.PreviousData = this.BindVariableResolver.ResolveVariables(tuple.SourceLineNumbers, objectField.PreviousData, false, out isDefault);
src/WixToolset.Core/Compiler.cs
+44 -4
@@ -7878,7 +7878,7 @@ namespace WixToolset.Core
7878 if (patch)
7879 {
7880 // /Patch/PatchProperty goes directly into MsiPatchMetadata table
7881 - this.Core.AddTuple(new MsiPatchMetadataTuple(sourceLineNumbers)
7881 + this.Core.AddTuple(new MsiPatchMetadataTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, company, name))
7882 {
7883 Company = company,
7884 Property = name,
@@ -8130,12 +8130,15 @@ namespace WixToolset.Core
8130 /// </summary>
8131 /// <param name="node">The element to parse.</param>
8132 /// <param name="diskId">Media index from parent element.</param>
8133 - private void ParsePatchBaselineElement(XElement node, int diskId)
8133 + private void ParsePatchBaselineElement(XElement node, int? diskId)
8134 {
8135 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8136 Identifier id = null;
8137 var parsedValidate = false;
8138 var validationFlags = TransformFlags.PatchTransformDefault;
8139 + string baselineFile = null;
8140 + string updateFile = null;
8141 + string transformFile = null;
8142
8143 foreach (var attrib in node.Attributes())
8144 {
@@ -8146,6 +8149,18 @@ namespace WixToolset.Core
8149 case "Id":
8150 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
8151 break;
8152 + case "DiskId":
8153 + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
8154 + break;
8155 + case "BaselineFile":
8156 + baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8157 + break;
8158 + case "UpdateFile":
8159 + updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8160 + break;
8161 + case "TransformFile":
8162 + transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8163 + break;
8164 default:
8165 this.Core.UnexpectedAttribute(node, attrib);
8166 break;
@@ -8167,6 +8182,28 @@ namespace WixToolset.Core
8182 this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27));
8183 }
8184
8185 + if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile))
8186 + {
8187 + if (String.IsNullOrEmpty(baselineFile))
8188 + {
8189 + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile"));
8190 + }
8191 +
8192 + if (String.IsNullOrEmpty(updateFile))
8193 + {
8194 + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile"));
8195 + }
8196 +
8197 + if (!String.IsNullOrEmpty(transformFile))
8198 + {
8199 + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile"));
8200 + }
8201 + }
8202 + else if (String.IsNullOrEmpty(transformFile))
8203 + {
8204 + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true));
8205 + }
8206 +
8207 foreach (var child in node.Elements())
8208 {
8209 if (CompilerCore.WixNamespace == child.Name.Namespace)
@@ -8200,8 +8237,11 @@ namespace WixToolset.Core
8237 {
8238 var tuple = new WixPatchBaselineTuple(sourceLineNumbers, id)
8239 {
8203 - DiskId = diskId,
8204 - ValidationFlags = validationFlags
8240 + DiskId = diskId ?? 1,
8241 + ValidationFlags = validationFlags,
8242 + BaselineFile = new IntermediateFieldPathValue { Path = baselineFile },
8243 + UpdateFile = new IntermediateFieldPathValue { Path = updateFile },
8244 + TransformFile = new IntermediateFieldPathValue { Path = transformFile }
8245 };
8246
8247 this.Core.AddTuple(tuple);
src/WixToolset.Core/Compiler_2.cs
-6
@@ -1028,12 +1028,6 @@ namespace WixToolset.Core
1028 Value = "0"
1029 });
1030
1031 - this.Core.AddTuple(new SummaryInformationTuple(sourceLineNumbers)
1032 - {
1033 - PropertyId = SumaryInformationType.WindowsInstallerVersion,
1034 - Value = msiVersion.ToString(CultureInfo.InvariantCulture)
1035 - });
1036 -
1031 this.Core.AddTuple(new SummaryInformationTuple(sourceLineNumbers)
1032 {
1033 PropertyId = SumaryInformationType.Security,
src/WixToolset.Core/Compiler_Patch.cs
+1 -2
@@ -197,9 +197,8 @@ namespace WixToolset.Core
197
198 if (!this.Core.EncounteredError)
199 {
200 - var tuple = new WixPatchIdTuple(sourceLineNumbers)
200 + var tuple = new WixPatchIdTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, patchId))
201 {
202 - ProductCode = patchId,
202 ClientPatchId = clientPatchId,
203 OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles,
204 ApiPatchingSymbolFlags = apiPatchingSymbolFlags
src/WixToolset.Core/Compiler_UI.cs
+3 -3
@@ -470,7 +470,7 @@ namespace WixToolset.Core
470 string defaultControl = null;
471 string cancelControl = null;
472
473 - this.ParseControlElement(child, id.Id, TupleDefinitionType.BBControl, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, false);
473 + this.ParseControlElement(child, id.Id, TupleDefinitionType.BBControl, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl);
474 break;
475 default:
476 this.Core.UnexpectedElement(node, child);
@@ -966,7 +966,7 @@ namespace WixToolset.Core
966 switch (child.Name.LocalName)
967 {
968 case "Control":
969 - this.ParseControlElement(child, id.Id, TupleDefinitionType.Control, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, trackDiskSpace);
969 + this.ParseControlElement(child, id.Id, TupleDefinitionType.Control, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl);
970 break;
971 default:
972 this.Core.UnexpectedElement(node, child);
@@ -1032,7 +1032,7 @@ namespace WixToolset.Core
1032 /// <param name="defaultControl">Name of the default control.</param>
1033 /// <param name="cancelControl">Name of the candle control.</param>
1034 /// <param name="trackDiskSpace">True if the containing dialog tracks disk space.</param>
1035 - private void ParseControlElement(XElement node, string dialog, TupleDefinitionType tupleType, ref IntermediateTuple lastTabTuple, ref string firstControl, ref string defaultControl, ref string cancelControl, bool trackDiskSpace)
1035 + private void ParseControlElement(XElement node, string dialog, TupleDefinitionType tupleType, ref IntermediateTuple lastTabTuple, ref string firstControl, ref string defaultControl, ref string cancelControl)
1036 {
1037 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1038 Identifier controlId = null;
src/WixToolset.Core/Resolver.cs
+1 -1
@@ -92,7 +92,7 @@ namespace WixToolset.Core
92 delayedFields = command.DelayedFields;
93 }
94
95 -#if REVISIT_FOR_PATCHING
95 +#if TODO_PATCHING
96 if (context.IntermediateRepresentation.SubStorages != null)
97 {
98 foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages)
src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs new
+112
@@ -0,0 +1,112 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace WixToolsetTest.CoreIntegration
4 +{
5 + using System.ComponentModel;
6 + using System.IO;
7 + using System.Linq;
8 + using System.Runtime.InteropServices;
9 + using System.Text;
10 + using System.Xml.Linq;
11 + using WixBuildTools.TestSupport;
12 + using WixToolset.Core.TestPackage;
13 + using Xunit;
14 +
15 + public class PatchFixture
16 + {
17 + private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd";
18 +
19 + [Fact(Skip = "Skip until patches have files in them")]
20 + public void CanBuildSimplePatch()
21 + {
22 + var folder = TestData.Get(@"TestData\PatchSingle");
23 +
24 + using (var fs = new DisposableFileSystem())
25 + {
26 + var tempFolder = fs.GetFolder();
27 +
28 + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0");
29 + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1");
30 + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1");
31 + var baselinePath = Path.ChangeExtension(baselinePdb, ".msp");
32 + var patchPath = Path.ChangeExtension(patchPdb, ".msp");
33 +
34 + Assert.True(File.Exists(baselinePdb));
35 + Assert.True(File.Exists(update1Pdb));
36 +
37 + var doc = GetExtractPatchXml(patchPath);
38 + Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value);
39 +
40 + var names = Query.GetSubStorageNames(patchPath);
41 + Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names);
42 +
43 + var cab = Path.Combine(tempFolder, "foo.cab");
44 + Query.ExtractStream(patchPath, "foo.cab", cab);
45 + Assert.True(File.Exists(cab));
46 +
47 + var files = Query.GetCabinetFiles(cab);
48 + Assert.Equal(new[] { "a", "b" }, files.Select(f => f.ArchiveName).ToArray()); // This test may not be quite correct, yet.
49 + }
50 + }
51 +
52 + private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB)
53 + {
54 + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
55 +
56 + var result = WixRunner.Execute(new[]
57 + {
58 + "build",
59 + Path.Combine(sourceFolder, @"Package.wxs"),
60 + "-d", "V=" + defineV,
61 + "-d", "A=" + defineA,
62 + "-d", "B=" + defineB,
63 + "-bindpath", Path.Combine(sourceFolder, ".data"),
64 + "-intermediateFolder", Path.Combine(baseFolder, "obj"),
65 + "-o", outputPath
66 + });
67 +
68 + result.AssertSuccess();
69 +
70 + return Path.ChangeExtension(outputPath, ".wixpdb");
71 + }
72 +
73 + private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV)
74 + {
75 + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
76 +
77 + var result = WixRunner.Execute(new[]
78 + {
79 + "build",
80 + Path.Combine(sourceFolder, @"Patch.wxs"),
81 + "-d", "V=" + defineV,
82 + "-bindpath", Path.Combine(baseFolder, "bin"),
83 + "-intermediateFolder", Path.Combine(baseFolder, "obj"),
84 + "-o", outputPath
85 + });
86 +
87 + result.AssertSuccess();
88 +
89 + return Path.ChangeExtension(outputPath, ".wixpdb");
90 + }
91 +
92 + private static XDocument GetExtractPatchXml(string path)
93 + {
94 + var buffer = new StringBuilder(65535);
95 + var size = buffer.Capacity;
96 +
97 + var er = MsiExtractPatchXMLData(path, 0, buffer, ref size);
98 + if (er != 0)
99 + {
100 + throw new Win32Exception(er);
101 + }
102 +
103 + return XDocument.Parse(buffer.ToString());
104 + }
105 +
106 + [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)]
107 + private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData);
108 +
109 + [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)]
110 + private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine);
111 + }
112 +}
src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs
-28
@@ -67,34 +67,6 @@ namespace WixToolsetTest.CoreIntegration
67 }
68 }
69
70 - [Fact]
71 - public void WixVersionVariablesWork()
72 - {
73 - var folder = TestData.Get(@"TestData\Variables");
74 -
75 - using (var fs = new DisposableFileSystem())
76 - {
77 - var baseFolder = fs.GetFolder();
78 - var intermediateFolder = Path.Combine(baseFolder, "obj");
79 -
80 - var result = WixRunner.Execute(new[]
81 - {
82 - "build",
83 - Path.Combine(folder, "Package.wxs"),
84 - Path.Combine(folder, "PackageComponents.wxs"),
85 - "-loc", Path.Combine(folder, "Package.en-us.wxl"),
86 - "-bindpath", Path.Combine(folder, "data"),
87 - "-intermediateFolder", intermediateFolder,
88 - "-o", Path.Combine(baseFolder, @"bin\test.msi")
89 - });
90 -
91 - result.AssertSuccess();
92 -
93 - var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.PreprocessorWarning);
94 - Assert.Single(warning);
95 - }
96 - }
97 -
70 [Fact]
71 public void ForEachLoopsWork()
72 {
src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt new
+1
@@ -0,0 +1 @@
1 +This is A v1.0.0
src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt new
+1
@@ -0,0 +1 @@
1 +This ia A v1.0.1
src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt new
+1
@@ -0,0 +1 @@
1 +This is B v1.0.0
src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt new
+1
@@ -0,0 +1 @@
1 +This ia B v1.0.1
src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs new
+31
@@ -0,0 +1,31 @@
1 +<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
2 + <Product Id='e703bf17-4765-444c-91fd-88550fa681d4' Name='~Test Package'
3 + Version='$(var.V)' Manufacturer='Example Corporation' Language='1033'
4 + UpgradeCode='e703bf17-4765-444c-91fd-88550fa681d4'>
5 + <Package InstallScope='perMachine' />
6 +
7 + <MajorUpgrade DowngradeErrorMessage='Newer version already installed.' />
8 + <MediaTemplate />
9 +
10 + <Directory Id='TARGETDIR' Name='SourceDir'>
11 + <Directory Id='ProgramFilesFolder'>
12 + <Directory Id='INSTALLFOLDER' Name='~Test App' />
13 + </Directory>
14 + </Directory>
15 +
16 + <Feature Id='Main'>
17 + <ComponentGroupRef Id='Components' />
18 + </Feature>
19 + </Product>
20 +
21 + <Fragment>
22 + <ComponentGroup Id="Components" Directory='INSTALLFOLDER'>
23 + <Component Id='A'>
24 + <File Name='a.txt' Source='Av$(var.A).txt' />
25 + </Component>
26 + <Component Id='B'>
27 + <File Name='b.txt' Source='Bv$(var.B).txt' />
28 + </Component>
29 + </ComponentGroup>
30 + </Fragment>
31 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs new
+23
@@ -0,0 +1,23 @@
1 +<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
2 + <Patch
3 + AllowRemoval="yes"
4 + Manufacturer="FireGiant"
5 + MoreInfoURL="http://www.example.com/"
6 + DisplayName="~Test Patch v$(var.V)"
7 + Description="~Test Small Update Patch v($var.V)"
8 + Classification="Update"
9 + >
10 +
11 + <Media Id="1" Cabinet="foo.cab">
12 + <PatchBaseline Id="RTM"/>
13 + </Media>
14 +
15 + <PatchFamilyRef Id='SamplePatchFamily'/>
16 + </Patch>
17 +
18 + <Fragment>
19 + <PatchFamily Id='SamplePatchFamily' Version='$(var.V)' Supersede='yes'>
20 + <ComponentRef Id="A"/>
21 + </PatchFamily>
22 + </Fragment>
23 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt new
+1
@@ -0,0 +1 @@
1 +This is A v1.0.0
src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt new
+1
@@ -0,0 +1 @@
1 +This ia A v1.0.1
src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt new
+1
@@ -0,0 +1 @@
1 +This is B v1.0.0
src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt new
+1
@@ -0,0 +1 @@
1 +This ia B v1.0.1
src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs new
+31
@@ -0,0 +1,31 @@
1 +<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
2 + <Product Id='7d326855-e790-4a94-8611-5351f8321fca' Name='~Test Package'
3 + Version='$(var.V)' Manufacturer='Example Corporation' Language='1033'
4 + UpgradeCode='7d326855-e790-4a94-8611-5351f8321fca'>
5 + <Package InstallScope='perMachine' Compressed='yes' />
6 +
7 + <MajorUpgrade DowngradeErrorMessage='Newer version already installed.' />
8 + <MediaTemplate EmbedCab='yes' />
9 +
10 + <Directory Id='TARGETDIR' Name='SourceDir'>
11 + <Directory Id='ProgramFilesFolder'>
12 + <Directory Id='INSTALLFOLDER' Name='~Test App' />
13 + </Directory>
14 + </Directory>
15 +
16 + <Feature Id='Main'>
17 + <ComponentGroupRef Id='Components' />
18 + </Feature>
19 + </Product>
20 +
21 + <Fragment>
22 + <ComponentGroup Id="Components" Directory='INSTALLFOLDER'>
23 + <Component>
24 + <File Id='a.txt' Name='a.txt' Source='Av$(var.A).txt' />
25 + </Component>
26 + <Component>
27 + <File Id='b.txt' Name='b.txt' Source='Bv$(var.B).txt' />
28 + </Component>
29 + </ComponentGroup>
30 + </Fragment>
31 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs new
+16
@@ -0,0 +1,16 @@
1 +<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
2 + <Patch
3 + AllowRemoval="yes"
4 + DisplayName="~Test Patch v$(var.V)"
5 + Description="~Test Small Update Patch v$(var.V)"
6 + MoreInfoURL="http://www.example.com/"
7 + Manufacturer="Example Corporation"
8 + Classification="Update">
9 +
10 + <Media Id="1" Cabinet="foo.cab">
11 + <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
12 + </Media>
13 +
14 + <PatchFamily Id='SequenceFamily' Version='$(var.V)' />
15 + </Patch>
16 +</Wix>
src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
+13 -1
@@ -1,4 +1,4 @@
1 -<?xml version="1.0" encoding="utf-8"?>
1 +<?xml version="1.0" encoding="utf-8"?>
2 <!-- 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. -->
3
4 <Project Sdk="Microsoft.NET.Sdk">
@@ -41,6 +41,18 @@
41 <Content Include="TestData\Font\TrueType.wxs" CopyToOutputDirectory="PreserveNewest" />
42 <Content Include="TestData\Icon\SampleIcon.wxs" CopyToOutputDirectory="PreserveNewest" />
43 <Content Include="TestData\LockPermissions\EmptyPermissions.wxs" CopyToOutputDirectory="PreserveNewest" />
44 + <Content Include="TestData\PatchFamilyFilter\.data\Av1.0.0.txt" CopyToOutputDirectory="PreserveNewest" />
45 + <Content Include="TestData\PatchFamilyFilter\.data\Av1.0.1.txt" CopyToOutputDirectory="PreserveNewest" />
46 + <Content Include="TestData\PatchFamilyFilter\.data\Bv1.0.0.txt" CopyToOutputDirectory="PreserveNewest" />
47 + <Content Include="TestData\PatchFamilyFilter\.data\Bv1.0.1.txt" CopyToOutputDirectory="PreserveNewest" />
48 + <Content Include="TestData\PatchFamilyFilter\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
49 + <Content Include="TestData\PatchFamilyFilter\Patch.wxs" CopyToOutputDirectory="PreserveNewest" />
50 + <Content Include="TestData\PatchSingle\.data\Av1.0.0.txt" CopyToOutputDirectory="PreserveNewest" />
51 + <Content Include="TestData\PatchSingle\.data\Av1.0.1.txt" CopyToOutputDirectory="PreserveNewest" />
52 + <Content Include="TestData\PatchSingle\.data\Bv1.0.0.txt" CopyToOutputDirectory="PreserveNewest" />
53 + <Content Include="TestData\PatchSingle\.data\Bv1.0.1.txt" CopyToOutputDirectory="PreserveNewest" />
54 + <Content Include="TestData\PatchSingle\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
55 + <Content Include="TestData\PatchSingle\Patch.wxs" CopyToOutputDirectory="PreserveNewest" />
56 <Content Include="TestData\SameFileFolders\data\a\test.txt" CopyToOutputDirectory="PreserveNewest" />
57 <Content Include="TestData\SameFileFolders\data\c\test.txt" CopyToOutputDirectory="PreserveNewest" />
58 <Content Include="TestData\SameFileFolders\data\b\test.txt" CopyToOutputDirectory="PreserveNewest" />