@joebigelow / wix-1 / commits / d529525a

Basic patching support

Rob Mensching committed May 30, 2020 at 14:53 UTC d529525a1e331f3ef9ec2707791c99bd78fdd82f
19 files changed +779 -986
src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs
+25 -41
@@ -147,7 +147,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
147 {
148 foreach (MediaRow transformMediaRow in transformMediaTable.Rows)
149 {
150 - if (mediaTuple.LastSequence < transformMediaRow.LastSequence)
150 + if (!mediaTuple.LastSequence.HasValue || mediaTuple.LastSequence < transformMediaRow.LastSequence)
151 {
152 // The Binder will pre-increment the sequence.
153 mediaTuple.LastSequence = transformMediaRow.LastSequence;
@@ -156,14 +156,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
156 }
157
158 // Use the Media/@DiskId if greater than the last sequence for backward compatibility.
159 - if (mediaTuple.LastSequence < mediaTuple.DiskId)
159 + if (!mediaTuple.LastSequence.HasValue || mediaTuple.LastSequence < mediaTuple.DiskId)
160 {
161 mediaTuple.LastSequence = mediaTuple.DiskId;
162 }
163
164 // Ignore media table in the transform.
165 mainTransform.Transform.Tables.Remove("Media");
166 - mainTransform.Transform.Tables.Remove("WixMedia");
166 mainTransform.Transform.Tables.Remove("MsiDigitalSignature");
167
168 var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchIdTuple, mainTransform.Transform, mediaTuple, baselineTuple, out var productCode);
@@ -767,15 +766,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
766 if (transform.TryGetTable("File", out var fileTable))
767 {
768 var componentWithChangedKeyPath = new Dictionary<string, string>();
770 - foreach (var row in fileTable.Rows)
769 + foreach (FileRow row in fileTable.Rows)
770 {
771 if (RowOperation.None == row.Operation)
772 {
773 continue;
774 }
775
777 - var fileId = row.FieldAsString(0);
778 - var componentId = row.FieldAsString(1);
776 + var fileId = row.File;
777 + var componentId = row.Component;
778
779 // If this file is the keypath of a component
780 if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal))
@@ -972,7 +971,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
971
972 if (row.Operation == RowOperation.None)
973 {
975 - // ignore the rows without section id.
974 + // Ignore the rows without section id.
975 if (isSectionIdEmpty)
976 {
977 currentTable.Rows.RemoveAt(i);
@@ -1157,47 +1156,33 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1156 return null;
1157 }
1158
1160 - // copy File table
1159 + // Copy File table
1160 if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count)
1161 {
1163 -#if TODO_PATCHING
1164 - // We require file source information.
1165 - var mainWixFileTable = mainTransform.Tables["WixFile"];
1166 - if (null == mainWixFileTable)
1167 - {
1168 - this.Messaging.Write(ErrorMessages.AdminImageRequired(productCode));
1169 - return null;
1170 - }
1171 -
1172 - var mainFileRows = new RowDictionary<FileRow>(mainFileTable);
1173 -
1162 var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition);
1175 - {
1176 - var mainFileRow = mainFileRows[mainWixFileRow.File];
1163
1178 - // set File.Sequence to non null to satisfy transform bind
1164 + foreach (FileRow mainFileRow in mainFileTable.Rows)
1165 + {
1166 + // Set File.Sequence to non null to satisfy transform bind.
1167 mainFileRow.Sequence = 1;
1168
1181 - // delete's don't need rows in the paired transform
1169 + // Delete's don't need rows in the paired transform.
1170 if (mainFileRow.Operation == RowOperation.Delete)
1171 {
1172 continue;
1173 }
1174
1187 - var pairedFileRow = (FileRow)pairedFileTable.CreateRow(null);
1175 + var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers);
1176 pairedFileRow.Operation = RowOperation.Modify;
1189 - for (var i = 0; i < mainFileRow.Fields.Length; i++)
1190 - {
1191 - pairedFileRow[i] = mainFileRow[i];
1192 - }
1177 + mainFileRow.CopyTo(pairedFileRow);
1178
1194 - // override authored media for patch bind
1195 - mainWixFileRow.DiskId = mediaTuple.DiskId;
1179 + // Override authored media for patch bind.
1180 + mainFileRow.DiskId = mediaTuple.DiskId;
1181
1197 - // suppress any change to File.Sequence to avoid bloat
1182 + // Suppress any change to File.Sequence to avoid bloat.
1183 mainFileRow.Fields[7].Modified = false;
1184
1200 - // force File row to appear in the transform
1185 + // Force File row to appear in the transform.
1186 switch (mainFileRow.Operation)
1187 {
1188 case RowOperation.Modify:
@@ -1211,19 +1196,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1196 break;
1197 }
1198 }
1214 -#endif
1199 }
1200
1201 // Add Media row to pairedTransform
1202 var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]);
1219 - var pairedMediaRow = pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers);
1203 + var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers);
1204 pairedMediaRow.Operation = RowOperation.Add;
1221 - pairedMediaRow[0] = mediaTuple.DiskId;
1222 - pairedMediaRow[1] = mediaTuple.LastSequence ?? 0;
1223 - pairedMediaRow[2] = mediaTuple.DiskPrompt;
1224 - pairedMediaRow[3] = mediaTuple.Cabinet;
1225 - pairedMediaRow[4] = mediaTuple.VolumeLabel;
1226 - pairedMediaRow[5] = mediaTuple.Source;
1205 + pairedMediaRow.DiskId = mediaTuple.DiskId;
1206 + pairedMediaRow.LastSequence = mediaTuple.LastSequence ?? 0;
1207 + pairedMediaRow.DiskPrompt = mediaTuple.DiskPrompt;
1208 + pairedMediaRow.Cabinet = mediaTuple.Cabinet;
1209 + pairedMediaRow.VolumeLabel = mediaTuple.VolumeLabel;
1210 + pairedMediaRow.Source = mediaTuple.Source;
1211
1212 // Add PatchPackage for this Media
1213 var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]);
@@ -1283,7 +1267,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1267 {
1268 var patchTargetTuples = tuples.OfType<WixPatchTargetTuple>().ToList();
1269
1286 - if (patchTargetTuples.Count > 0)
1270 + if (patchTargetTuples.Any())
1271 {
1272 var targets = new SortedSet<string>();
1273 var replace = true;
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+26 -104
@@ -43,7 +43,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
43 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
44 this.DelayedFields = context.DelayedFields;
45 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
46 - this.FileSystemExtensions = context.FileSystemExtensions;
46 + this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions);
47 this.Intermediate = context.IntermediateRepresentation;
48 this.IntermediateFolder = context.IntermediateFolder;
49 this.OutputPath = context.OutputPath;
@@ -79,7 +79,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
79
80 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; }
81
82 - public IEnumerable<IFileSystemExtension> FileSystemExtensions { get; }
82 + public FileSystemManager FileSystemManager { get; }
83
84 public bool DeltaBinaryPatch { get; set; }
85
@@ -116,7 +116,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
116 var trackedFiles = new List<ITrackedFile>();
117
118 var containsMergeModules = false;
119 - var suppressedTableNames = new HashSet<string>();
119
120 // If there are any fields to resolve later, create the cache to populate during bind.
121 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null;
@@ -226,7 +225,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
225 return null;
226 }
227
229 - WindowsInstallerData output;
228 this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound);
229 this.Messaging.Write(VerboseMessages.UpdatingFileInformation());
230
@@ -268,14 +266,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
266 }
267 else if (SectionType.Patch == section.Type)
268 {
271 - // Merge transform data into the output object.
272 - //IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
273 -
274 - //var command = new CopyTransformDataCommand(this.Messaging, /*output*/this.SubStorages, tableDefinitions, copyOutFileRows: true);
275 - //command.Output = output;
276 - //command.TableDefinitions = this.TableDefinitions;
277 - //command.CopyOutFileRows = true;
278 - var command = new GetFileFacadesFromTransforms(this.Messaging, this.SubStorages, tableDefinitions);
269 + var command = new GetFileFacadesFromTransforms(this.Messaging, this.FileSystemManager, this.SubStorages);
270 command.Execute();
271 var filesFromTransforms = command.FileFacades;
272
@@ -338,6 +329,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
329 command.Execute();
330 }
331
332 + // Update file sequence.
333 + {
334 + var command = new UpdateMediaSequencesCommand(section, fileFacades);
335 + command.Execute();
336 + }
337 +
338 // stop processing if an error previously occurred
339 if (this.Messaging.EncounteredError)
340 {
@@ -345,6 +342,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
342 }
343
344 // Time to create the output object. Try to put as much above here as possible, updating the IR is better.
345 + WindowsInstallerData output;
346 {
347 var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions, this.WindowsInstallerBackendHelper);
348 command.Execute();
@@ -352,14 +350,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
350 output = command.Output;
351 }
352
355 - // Update file sequence.
356 - {
357 - var command = new UpdateMediaSequencesCommand(output, fileFacades);
358 - command.Execute();
359 - }
360 -
353 // Modularize identifiers.
362 - if (OutputType.Module == output.Type)
354 + if (output.Type == OutputType.Module)
355 {
356 var command = new ModularizeCommand(output, modularizationSuffix, section.Tuples.OfType<WixSuppressModularizationTuple>());
357 command.Execute();
@@ -457,18 +449,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
449 trackedFiles.AddRange(command.TrackedFiles);
450 }
451
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 -
468 - this.CopyToTransformData(this.Output);
469 -#endif
470 - }
471 -#endif
452 + if (output.Type == OutputType.Patch)
453 + {
454 + // Copy output data back into the transforms.
455 + var command = new UpdateTransformsWithFileFacades(this.Messaging, output, this.SubStorages, tableDefinitions, fileFacades);
456 + command.Execute();
457 + }
458
459 // stop processing if an error previously occurred
460 if (this.Messaging.EncounteredError)
@@ -483,8 +469,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
469 var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final);
470 trackedFiles.Add(trackMsi);
471
486 - var temporaryFiles = this.GenerateDatabase(output, tableDefinitions, trackMsi.Path, false, false);
487 - trackedFiles.AddRange(temporaryFiles);
472 + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, trackMsi.Path, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false);
473 + command.Execute();
474 +
475 + trackedFiles.AddRange(command.GeneratedTemporaryFiles);
476 }
477
478 // Stop processing if an error previously occurred.
@@ -498,31 +486,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
486 {
487 this.Messaging.Write(VerboseMessages.MergingModules());
488
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 - {
506 - var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString();
507 - var sequenceTable = output.Tables[sequenceTableName];
508 -
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 - }
519 -
489 var command = new MergeModulesCommand(this.Messaging);
490 command.FileFacades = fileFacades;
491 command.IntermediateFolder = this.IntermediateFolder;
492 command.Output = output;
493 command.OutputPath = this.OutputPath;
525 - command.SuppressedTableNames = suppressedTableNames;
494 + command.TableDefinitions = tableDefinitions;
495 command.Execute();
496 }
497
@@ -607,38 +576,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
576 return wixout;
577 }
578
610 -#if TODO_PATCHING
611 - /// <summary>
612 - /// Copy file data between transform substorages and the patch output object
613 - /// </summary>
614 - /// <param name="output">The output to bind.</param>
615 - /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
616 - private IEnumerable<FileFacade> CopyFromTransformData(Output output)
617 - {
618 - var command = new CopyTransformDataCommand();
619 - command.CopyOutFileRows = true;
620 - command.Output = output;
621 - command.TableDefinitions = this.TableDefinitions;
622 - command.Execute();
623 -
624 - return command.FileFacades;
625 - }
626 -
627 - /// <summary>
628 - /// Copy file data between transform substorages and the patch output object
629 - /// </summary>
630 - /// <param name="output">The output to bind.</param>
631 - /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
632 - private void CopyToTransformData(Output output)
633 - {
634 - var command = new CopyTransformDataCommand();
635 - command.CopyOutFileRows = false;
636 - command.Output = output;
637 - command.TableDefinitions = this.TableDefinitions;
638 - command.Execute();
639 - }
640 -#endif
641 -
579 /// <summary>
580 /// Validate that there are no duplicate GUIDs in the output.
581 /// </summary>
@@ -650,7 +587,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
587 {
588 if (output.TryGetTable("Component", out var componentTable))
589 {
653 - Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
590 + var componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
591
592 foreach (Data.WindowsInstaller.Rows.ComponentRow row in componentTable.Rows)
593 {
@@ -658,12 +595,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
595 // there's already an error that prevented it from being replaced with a real GUID.
596 if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid)
597 {
661 - bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
662 - bool allComponentsHaveConditions = thisComponentHasCondition;
598 + var thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
599 + var allComponentsHaveConditions = thisComponentHasCondition;
600
601 if (componentGuidConditions.ContainsKey(row.Guid))
602 {
666 - allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition;
603 + allComponentsHaveConditions = thisComponentHasCondition && componentGuidConditions[row.Guid];
604
605 if (allComponentsHaveConditions)
606 {
@@ -728,20 +665,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind
665
666 return layout;
667 }
731 -
732 - /// <summary>
733 - /// Creates the MSI/MSM/PCP database.
734 - /// </summary>
735 - /// <param name="output">Output to create database for.</param>
736 - /// <param name="databaseFile">The database file to create.</param>
737 - /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
738 - /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
739 - private IEnumerable<ITrackedFile> GenerateDatabase(WindowsInstallerData output, TableDefinitionCollection tableDefinitions, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
740 - {
741 - var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemExtensions, output, databaseFile, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns, this.SuppressAddingValidationRows, useSubdirectory);
742 - command.Execute();
743 -
744 - return command.GeneratedTemporaryFiles;
745 - }
668 }
669 }
src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+5 -27
@@ -3,23 +3,21 @@
3 namespace WixToolset.Core.WindowsInstaller.Bind
4 {
5 using System;
6 - using System.Collections.Generic;
6 using System.Globalization;
7 using System.IO;
8 using WixToolset.Core.WindowsInstaller.Msi;
9 using WixToolset.Data;
10 using WixToolset.Data.Tuples;
11 using WixToolset.Data.WindowsInstaller;
13 - using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13
14 internal class BindTransformCommand
15 {
18 - public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IFileSystemExtension> extensions, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions)
16 + public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions)
17 {
18 this.Messaging = messaging;
19 this.BackendHelper = backendHelper;
22 - this.Extensions = extensions;
20 + this.FileSystemManager = fileSystemManager;
21 this.IntermediateFolder = intermediateFolder;
22 this.Transform = transform;
23 this.OutputPath = outputPath;
@@ -30,7 +28,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
28
29 private IBackendHelper BackendHelper { get; }
30
33 - private IEnumerable<IFileSystemExtension> Extensions { get; }
31 + private FileSystemManager FileSystemManager { get; }
32
33 private TableDefinitionCollection TableDefinitions { get; }
34
@@ -353,7 +351,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
351 targetRow[i] = emptyFile;
352 modifiedRow = true;
353 }
356 - else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data))
354 + else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data))
355 {
356 targetRow[i] = objectField.PreviousData;
357 modifiedRow = true;
@@ -438,29 +436,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
436 }
437 }
438
441 - private bool CompareFiles(string targetFile, string updatedFile)
442 - {
443 - bool? compared = null;
444 - foreach (var extension in this.Extensions)
445 - {
446 - compared = extension.CompareFiles(targetFile, updatedFile);
447 - if (compared.HasValue)
448 - {
449 - break;
450 - }
451 - }
452 -
453 - if (!compared.HasValue)
454 - {
455 - throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
456 - }
457 -
458 - return compared.Value;
459 - }
460 -
439 private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns)
440 {
463 - var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.Extensions, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true );
441 + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true);
442 command.Execute();
443 }
444 }
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 TODO_PATCHING
36 +#if TODO_PATCHING_DELTA
37 foreach (FileFacade facade in this.FileFacades)
38 {
39 if (RowOperation.Modify == facade.File.Operation &&
src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+2 -1
@@ -499,7 +499,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
499 row.FileSize = tuple.FileSize;
500 row.Version = tuple.Version;
501 row.Language = tuple.Language;
502 - row.DiskId = tuple.DiskId ?? 1; // TODO: is 0 the correct thing to default here
502 + row.DiskId = tuple.DiskId ?? 1; // TODO: is 1 the correct thing to default here
503 + row.Sequence = tuple.Sequence;
504 row.Source = tuple.Source.Path;
505
506 var attributes = (tuple.Attributes & FileTupleAttributes.Checksum) == FileTupleAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0;
src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
+1 -1
@@ -45,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
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);
48 + var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false);
49 transform = command.Execute();
50 }
51 else
src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs new
+71
@@ -0,0 +1,71 @@
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.Collections.Generic;
6 + using System.IO;
7 + using WixToolset.Extensibility;
8 +
9 + internal class FileSystemManager
10 + {
11 + public FileSystemManager(IEnumerable<IFileSystemExtension> fileSystemExtensions)
12 + {
13 + this.Extensions = fileSystemExtensions;
14 + }
15 +
16 + private IEnumerable<IFileSystemExtension> Extensions { get; }
17 +
18 + public bool CompareFiles(string firstPath, string secondPath)
19 + {
20 + foreach (var extension in this.Extensions)
21 + {
22 + var compared = extension.CompareFiles(firstPath, secondPath);
23 + if (compared.HasValue)
24 + {
25 + return compared.Value;
26 + }
27 + }
28 +
29 + return BuiltinCompareFiles(firstPath, secondPath);
30 + }
31 +
32 + private static bool BuiltinCompareFiles(string firstPath, string secondPath)
33 + {
34 + using (var firstStream = File.OpenRead(firstPath))
35 + using (var secondStream = File.OpenRead(secondPath))
36 + {
37 + if (firstStream.Length != secondStream.Length)
38 + {
39 + return false;
40 + }
41 +
42 + // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance.
43 + // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined.
44 + var firstBuffer = new byte[16 * 1024];
45 + var secondBuffer = new byte[16 * 1024];
46 +
47 + var firstReadLength = 0;
48 + do
49 + {
50 + firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length);
51 + var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length);
52 +
53 + if (firstReadLength != secondReadLength)
54 + {
55 + return false;
56 + }
57 +
58 + for (var i = 0; i < firstReadLength; ++i)
59 + {
60 + if (firstBuffer[i] != secondBuffer[i])
61 + {
62 + return false;
63 + }
64 + }
65 + } while (0 < firstReadLength);
66 + }
67 +
68 + return true;
69 + }
70 + }
71 +}
src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+4 -5
@@ -10,17 +10,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
10 using WixToolset.Core.WindowsInstaller.Msi;
11 using WixToolset.Data;
12 using WixToolset.Data.WindowsInstaller;
13 - using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 internal class GenerateDatabaseCommand
17 {
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)
18 + public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory)
19 {
20 this.Messaging = messaging;
21 this.BackendHelper = backendHelper;
23 - this.Extensions = fileSystemExtensions;
22 + this.FileSystemManager = fileSystemManager;
23 this.Data = data;
24 this.OutputPath = outputPath;
25 this.TableDefinitions = tableDefinitions;
@@ -35,7 +34,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
34
35 private IBackendHelper BackendHelper { get; }
36
38 - private IEnumerable<IFileSystemExtension> Extensions { get; }
37 + private FileSystemManager FileSystemManager { get; }
38
39 /// <summary>
40 /// Whether to keep columns added in a transform.
@@ -371,7 +370,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
370 var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst"));
371
372 // Bind the transform.
374 - var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.Extensions, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions);
373 + var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions);
374 command.Execute();
375
376 if (this.Messaging.EncounteredError)
src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
+5 -8
@@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller
10 using WixToolset.Data.Tuples;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Data.WindowsInstaller.Rows;
13 - using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Services;
14
15 /// <summary>
@@ -25,11 +24,12 @@ namespace WixToolset.Core.WindowsInstaller
24 /// <summary>
25 /// Instantiates a new Differ class.
26 /// </summary>
28 - public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool showPedanticMessages)
27 + public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages)
28 {
29 this.messaging = messaging;
30 this.TargetOutput = targetOutput;
31 this.UpdatedOutput = updatedOutput;
32 + this.PreserveUnchangedRows = preserveUnchangedRows;
33 this.ShowPedanticMessages = showPedanticMessages;
34 }
35
@@ -111,10 +111,10 @@ namespace WixToolset.Core.WindowsInstaller
111 }
112 else if (TableOperation.None == operation)
113 {
114 - var modified = transform.EnsureTable(updatedTable.Definition);
114 + var modifiedTable = transform.EnsureTable(updatedTable.Definition);
115 foreach (var row in rows)
116 {
117 - modified.Rows.Add(row);
117 + modifiedTable.Rows.Add(row);
118 }
119 }
120 }
@@ -242,10 +242,7 @@ namespace WixToolset.Core.WindowsInstaller
242 {
243 var columnDefinition = updatedRow.Fields[i].Column;
244
245 - if (columnDefinition.Unreal)
246 - {
247 - }
248 - else if (!columnDefinition.PrimaryKey)
245 + if (!columnDefinition.PrimaryKey)
246 {
247 var modified = false;
248
src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
+6 -10
@@ -26,28 +26,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind
26 var facades = new List<FileFacade>();
27
28 var assemblyFile = this.Section.Tuples.OfType<AssemblyTuple>().ToDictionary(t => t.Id.Id);
29 - //var wixFiles = this.Section.Tuples.OfType<WixFileTuple>().ToDictionary(t => t.Id.Id);
29 //var deltaPatchFiles = this.Section.Tuples.OfType<WixDeltaPatchFileTuple>().ToDictionary(t => t.Id.Id);
30
31 foreach (var file in this.Section.Tuples.OfType<FileTuple>())
32 {
34 - //var wixFile = wixFiles[file.Id.Id];
33 + assemblyFile.TryGetValue(file.Id.Id, out var assembly);
34
35 //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile);
36
38 - //facades.Add(new FileFacade(file, wixFile, deltaPatchFile));
39 -
40 - assemblyFile.TryGetValue(file.Id.Id, out var assembly);
41 -
37 facades.Add(new FileFacade(file, assembly));
38 + //facades.Add(new FileFacade(file, wixFile, deltaPatchFile));
39 }
40
45 - //this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
41 +#if TODO_PATCHING_DELTA
42 + this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
43 +#endif
44
45 this.FileFacades = facades;
46 }
47
50 -#if FIX_THIS
48 +#if TODO_PATCHING_DELTA
49 /// <summary>
50 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
51 /// </summary>
@@ -132,7 +130,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
130 file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths);
131 }
132
135 -#if TODO_PATCHING
133 Field field = row.Fields[2];
134 if (null != field.PreviousData)
135 {
@@ -145,7 +142,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
142 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
143 }
144 }
148 -#endif
145 }
146 #endif
147 }
src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs
+80 -496
@@ -4,12 +4,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
4 {
5 using System;
6 using System.Collections.Generic;
7 - using System.Diagnostics;
8 - using System.IO;
7 using System.Linq;
8 using WixToolset.Core.Bind;
9 using WixToolset.Data;
12 - using WixToolset.Data.Tuples;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Data.WindowsInstaller.Rows;
12 using WixToolset.Extensibility;
@@ -17,90 +14,39 @@ namespace WixToolset.Core.WindowsInstaller.Bind
14
15 internal class GetFileFacadesFromTransforms
16 {
20 - public GetFileFacadesFromTransforms(IMessaging messaging, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions)
17 + public GetFileFacadesFromTransforms(IMessaging messaging, FileSystemManager fileSystemManager, IEnumerable<SubStorage> subStorages)
18 {
19 this.Messaging = messaging;
20 + this.FileSystemManager = fileSystemManager;
21 this.SubStorages = subStorages;
24 - this.TableDefinitions = tableDefinitions;
22 }
23
27 - public IEnumerable<IFileSystemExtension> Extensions { get; }
28 -
24 private IMessaging Messaging { get; }
25
31 - public IEnumerable<SubStorage> SubStorages { get; }
26 + private FileSystemManager FileSystemManager { get; }
27
33 - private TableDefinitionCollection TableDefinitions { get; }
28 + private IEnumerable<SubStorage> SubStorages { get; }
29
30 public IEnumerable<FileFacade> FileFacades { get; private set; }
31
32 public void Execute()
33 {
34 var allFileRows = new List<FileFacade>();
40 - var copyToPatch = (allFileRows != null);
41 -#if TODO_PATCHING
42 - var patchMediaRows = new RowDictionary<MediaRow>();
35
44 - var patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
36 + var patchMediaRows = new RowDictionary<MediaRow>();
37
46 - var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
47 - var patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
38 + var patchMediaFileRows = new Dictionary<int, RowDictionary<FileRow>>();
39
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 - }
40 + //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
41
42 // 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);
43 + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data);
44
71 - foreach (var substorage in this.SubStorages)
45 + // Enumerate through main transforms.
46 + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#")))
47 {
73 - if (substorage.Name.StartsWith("#"))
74 - {
75 - // Skip paired transforms.
76 - continue;
77 - }
78 -
48 var mainTransform = substorage.Data;
80 - var mainWixFiles = new RowDictionary<WixFileRow>(mainTransform.Tables["WixFile"]);
81 - var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
82 -
49 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 - }
50
51 if (null == mainFileTable)
52 {
@@ -108,74 +54,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
54 }
55
56 // Index File table of pairedTransform
57 + var pairedTransform = pairedTransforms["#" + substorage.Name];
58 var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
59
113 - foreach (FileRow mainFileRow in mainFileTable.Rows)
60 + foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete))
61 {
115 - if (RowOperation.Delete == mainFileRow.Operation)
116 - {
117 - continue;
118 - }
119 - else if (RowOperation.None == mainFileRow.Operation)
120 - {
121 - continue;
122 - }
62 + var mainFileId = mainFileRow.File;
63
124 - var mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
64 + // We need compare the underlying files and include all file changes.
65 + var objectField = (ObjectField)mainFileRow.Fields[9];
66 + var pairedFileRow = pairedFileRows.Get(mainFileId);
67
126 - if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
68 + // If the file is new, we always need to add it to the patch.
69 + if (mainFileRow.Operation == RowOperation.Add)
70 {
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
71 + if (null != pairedFileRow) // RowOperation.Add
72 {
73 // Always patch-added, but never non-compressed.
74 pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
@@ -184,402 +77,93 @@ namespace WixToolset.Core.WindowsInstaller.Bind
77 pairedFileRow.Operation = RowOperation.Add;
78 }
79 }
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)
80 + else
81 {
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
82 + // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
83 + if (null == objectField.PreviousData)
84 {
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))
85 + if (mainFileRow.Operation == RowOperation.None)
86 {
220 - this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
87 + continue;
88 }
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));
89 }
90 else
91 {
438 - sequence = (duplicateFilesSequence + installFilesSequence) / 2;
439 - if (installFilesSequence == sequence || duplicateFilesSequence == sequence)
92 + // TODO: should this entire condition be placed in the binder file manager?
93 + if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/
94 + !this.FileSystemManager.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
95 {
441 - throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
96 + // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
97 + mainFileRow.Operation = RowOperation.Modify;
98 + if (null != pairedFileRow)
99 + {
100 + // Always patch-added, but never non-compressed.
101 + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
102 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
103 + pairedFileRow.Fields[6].Modified = true;
104 + pairedFileRow.Operation = RowOperation.Modify;
105 + }
106 + }
107 + else
108 + {
109 + // The File is same. We need mark all the attributes as unchanged.
110 + mainFileRow.Operation = RowOperation.None;
111 + foreach (var field in mainFileRow.Fields)
112 + {
113 + field.Modified = false;
114 + }
115 +
116 + if (null != pairedFileRow)
117 + {
118 + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
119 + pairedFileRow.Fields[6].Modified = false;
120 + pairedFileRow.Operation = RowOperation.None;
121 + }
122 + continue;
123 }
124 }
125 }
445 - else
446 - {
447 - sequence = installFilesSequence + 1;
448 - }
449 - }
126
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 - }
127 + // index patch files by diskId+fileId
128 + var diskId = mainFileRow.DiskId;
129
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)
130 + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
131 {
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;
132 + mediaFileRows = new RowDictionary<FileRow>();
133 + patchMediaFileRows.Add(diskId, mediaFileRows);
134 }
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 - }
135
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 - }
136 + var patchFileRow = mediaFileRows.Get(mainFileId);
137
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 - }
138 + if (null == patchFileRow)
139 + {
140 + //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
141 + patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers);
142 + mainFileRow.CopyTo(patchFileRow);
143
534 - var fileId = row.FieldAsString(0);
535 - var componentId = row.FieldAsString(1);
144 + mediaFileRows.Add(patchFileRow);
145
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);
146 + allFileRows.Add(new FileFacade(patchFileRow)); // TODO: should we be passing along delta information? Probably, right?
147 }
544 - }
545 - else
546 - {
547 - if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
148 + else
149 {
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 - }
150 + // TODO: confirm the rest of data is identical?
151
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);
152 + // make sure Source is same. Otherwise we are silently ignoring a file.
153 + if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase))
154 + {
155 + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source));
156 + }
157
571 - if (compared.HasValue)
572 - {
573 - break;
158 +#if TODO_PATCHING_DELTA
159 + // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
160 + patchFileRow.AppendPreviousDataFrom(mainFileRow);
161 +#endif
162 + }
163 }
164 }
165
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;
166 + this.FileFacades = allFileRows;
167 }
168 }
169 }
src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+43 -16
@@ -12,6 +12,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
12 using WixToolset.Core.Native;
13 using WixToolset.Core.WindowsInstaller.Msi;
14 using WixToolset.Data;
15 + using WixToolset.Data.Tuples;
16 using WixToolset.Data.WindowsInstaller;
17 using WixToolset.Data.WindowsInstaller.Rows;
18 using WixToolset.Extensibility.Services;
@@ -34,7 +35,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
35
36 public string OutputPath { private get; set; }
37
37 - public IEnumerable<string> SuppressedTableNames { private get; set; }
38 + public TableDefinitionCollection TableDefinitions { private get; set; }
39
40 public string IntermediateFolder { private get; set; }
41
@@ -49,6 +50,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50 return;
51 }
52
53 + var suppressedTableNames = this.AddBackSuppresedSequenceTables();
54 +
55 IMsmMerge2 merge = null;
56 bool commit = true;
57 bool logOpen = false;
@@ -212,9 +215,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
215 return;
216 }
217
215 - using (Database db = new Database(this.OutputPath, OpenDatabase.Direct))
218 + using (var db = new Database(this.OutputPath, OpenDatabase.Direct))
219 {
217 - Table suppressActionTable = this.Output.Tables["WixSuppressAction"];
220 + var suppressActionTable = this.Output.Tables["WixSuppressAction"];
221
222 // suppress individual actions
223 if (null != suppressActionTable)
@@ -239,40 +242,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind
242 }
243
244 // query for merge module actions in suppressed sequences and drop them
242 - foreach (string tableName in this.SuppressedTableNames)
245 + foreach (var tableName in suppressedTableNames)
246 {
247 if (!db.TableExists(tableName))
248 {
249 continue;
250 }
251
249 - using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
252 + using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
253 {
251 - foreach (Record resultRecord in view.Records)
254 + foreach (var resultRecord in view.Records)
255 {
256 this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName));
257 }
258 }
259
260 // drop suppressed sequences
258 - using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
261 + using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
262 {
263 }
264
265 // delete the validation rows
263 - using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
266 + using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
267 + using (var record = new Record(1))
268 {
265 - using (Record record = new Record(1))
266 - {
267 - record.SetString(1, tableName);
268 - view.Execute(record);
269 - }
269 + record.SetString(1, tableName);
270 + view.Execute(record);
271 }
272 }
273
274 // now update the Attributes column for the files from the Merge Modules
275 this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles());
275 - using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
276 + using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
277 {
278 foreach (var file in this.FileFacades)
279 {
@@ -281,13 +282,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
282 continue;
283 }
284
284 - using (Record record = new Record(1))
285 + using (var record = new Record(1))
286 {
287 record.SetString(1, file.Id);
288 view.Execute(record);
289 }
290
290 - using (Record recordUpdate = view.Fetch())
291 + using (var recordUpdate = view.Fetch())
292 {
293 if (null == recordUpdate)
294 {
@@ -332,5 +333,31 @@ namespace WixToolset.Core.WindowsInstaller.Bind
333 db.Commit();
334 }
335 }
336 +
337 + private IEnumerable<string> AddBackSuppresedSequenceTables()
338 + {
339 + // Add back possibly suppressed sequence tables since all sequence tables must be present
340 + // for the merge process to work. We'll drop the suppressed sequence tables again as
341 + // necessary.
342 + var suppressedTableNames = new HashSet<string>();
343 +
344 + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
345 + {
346 + var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString();
347 + var sequenceTable = this.Output.Tables[sequenceTableName];
348 +
349 + if (null == sequenceTable)
350 + {
351 + sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]);
352 + }
353 +
354 + if (0 == sequenceTable.Rows.Count)
355 + {
356 + suppressedTableNames.Add(sequenceTableName);
357 + }
358 + }
359 +
360 + return suppressedTableNames;
361 + }
362 }
363 }
src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs
-227
@@ -2,14 +2,7 @@
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;
5 using WixToolset.Data.WindowsInstaller;
12 - using WixToolset.Extensibility;
6
7 internal class PatchTransform
8 {
@@ -22,225 +15,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind
15 public string Baseline { get; }
16
17 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 - }
18 }
19 }
src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+7 -9
@@ -39,20 +39,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
39
40 public void Execute()
41 {
42 + var assemblyNameTuples = this.Section.Tuples.OfType<MsiAssemblyNameTuple>().ToDictionary(t => t.Id.Id);
43 +
44 foreach (var file in this.UpdateFileFacades)
45 {
44 - this.UpdateFileFacade(file);
46 + this.UpdateFileFacade(file, assemblyNameTuples);
47 }
48 }
49
48 - private void UpdateFileFacade(FileFacade facade)
50 + private void UpdateFileFacade(FileFacade facade, Dictionary<string, MsiAssemblyNameTuple> assemblyNameTuples)
51 {
50 - var assemblyNameTuples = new Dictionary<string, MsiAssemblyNameTuple>();
51 - foreach (var assemblyTuple in this.Section.Tuples.OfType<MsiAssemblyNameTuple>())
52 - {
53 - assemblyNameTuples.Add(assemblyTuple.ComponentRef + "/" + assemblyTuple.Name, assemblyTuple);
54 - }
55 -
52 FileInfo fileInfo = null;
53 try
54 {
@@ -335,7 +331,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
331 var lookup = String.Concat(facade.ComponentRef, "/", name);
332 if (!assemblyNameTuples.TryGetValue(lookup, out var assemblyNameTuple))
333 {
338 - assemblyNameTuple = this.Section.AddTuple(new MsiAssemblyNameTuple(facade.SourceLineNumber)
334 + assemblyNameTuple = this.Section.AddTuple(new MsiAssemblyNameTuple(facade.SourceLineNumber, new Identifier(AccessModifier.Private, facade.ComponentRef, name))
335 {
336 ComponentRef = facade.ComponentRef,
337 Name = name,
@@ -348,6 +344,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
344 }
345
346 facade.AssemblyNames.Add(assemblyNameTuple);
347 +
348 + assemblyNameTuples.Add(assemblyNameTuple.Id.Id, assemblyNameTuple);
349 }
350
351 assemblyNameTuple.Value = value;
src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+28 -33
@@ -6,62 +6,59 @@ namespace WixToolset.Core.WindowsInstaller.Bind
6 using System.Linq;
7 using WixToolset.Core.Bind;
8 using WixToolset.Data;
9 - using WixToolset.Data.WindowsInstaller;
10 - using WixToolset.Data.WindowsInstaller.Rows;
9 + using WixToolset.Data.Tuples;
10
11 internal class UpdateMediaSequencesCommand
12 {
14 - public UpdateMediaSequencesCommand(WindowsInstallerData output, List<FileFacade> fileFacades)
13 + public UpdateMediaSequencesCommand(IntermediateSection section, List<FileFacade> fileFacades)
14 {
16 - this.Output = output;
15 + this.Section = section;
16 this.FileFacades = fileFacades;
17 }
18
20 - private WindowsInstallerData Output { get; }
19 + private IntermediateSection Section { get; }
20
21 private List<FileFacade> FileFacades { get; }
22
23 public void Execute()
24 {
26 - var fileRows = new RowDictionary<FileRow>(this.Output.Tables["File"]);
27 - var mediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]);
25 + var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
26
27 // Calculate sequence numbers and media disk id layout for all file media information objects.
30 - if (OutputType.Module == this.Output.Type)
28 + if (SectionType.Module == this.Section.Type)
29 {
30 var lastSequence = 0;
31
32 // Order by Component to group the files by directory.
33 var optimized = this.OptimizedFileFacades();
36 - foreach (var fileId in optimized.Select(f => f.Id))
34 + foreach (var facade in optimized)
35 {
38 - var fileRow = fileRows.Get(fileId);
39 - fileRow.Sequence = ++lastSequence;
36 + facade.Sequence = ++lastSequence;
37 }
38 }
39 else
40 {
41 var lastSequence = 0;
45 - MediaRow mediaRow = null;
42 + MediaTuple mediaTuple = null;
43 var patchGroups = new Dictionary<int, List<FileFacade>>();
44
45 // sequence the non-patch-added files
46 var optimized = this.OptimizedFileFacades();
47 foreach (var facade in optimized)
48 {
52 - if (null == mediaRow)
49 + if (null == mediaTuple)
50 {
54 - mediaRow = mediaRows.Get(facade.DiskId);
55 - if (OutputType.Patch == this.Output.Type)
51 + mediaTuple = mediaRows[facade.DiskId];
52 + if (SectionType.Patch == this.Section.Type)
53 {
54 // patch Media cannot start at zero
58 - lastSequence = mediaRow.LastSequence;
55 + lastSequence = mediaTuple.LastSequence ?? 1;
56 }
57 }
61 - else if (mediaRow.DiskId != facade.DiskId)
58 + else if (mediaTuple.DiskId != facade.DiskId)
59 {
63 - mediaRow.LastSequence = lastSequence;
64 - mediaRow = mediaRows.Get(facade.DiskId);
60 + mediaTuple.LastSequence = lastSequence;
61 + mediaTuple = mediaRows[facade.DiskId];
62 }
63
64 if (facade.PatchGroup.HasValue)
@@ -76,15 +73,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
73 }
74 else if (!facade.FromModule)
75 {
79 - var fileRow = fileRows.Get(facade.Id);
80 - fileRow.Sequence = ++lastSequence;
76 + facade.Sequence = ++lastSequence;
77 }
78 }
79
84 - if (null != mediaRow)
80 + if (null != mediaTuple)
81 {
86 - mediaRow.LastSequence = lastSequence;
87 - mediaRow = null;
82 + mediaTuple.LastSequence = lastSequence;
83 + mediaTuple = null;
84 }
85
86 // sequence the patch-added files
@@ -92,24 +88,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind
88 {
89 foreach (var facade in patchGroup)
90 {
95 - if (null == mediaRow)
91 + if (null == mediaTuple)
92 {
97 - mediaRow = mediaRows.Get(facade.DiskId);
93 + mediaTuple = mediaRows[facade.DiskId];
94 }
99 - else if (mediaRow.DiskId != facade.DiskId)
95 + else if (mediaTuple.DiskId != facade.DiskId)
96 {
101 - mediaRow.LastSequence = lastSequence;
102 - mediaRow = mediaRows.Get(facade.DiskId);
97 + mediaTuple.LastSequence = lastSequence;
98 + mediaTuple = mediaRows[facade.DiskId];
99 }
100
105 - var fileRow = fileRows.Get(facade.Id);
106 - fileRow.Sequence = ++lastSequence;
101 + facade.Sequence = ++lastSequence;
102 }
103 }
104
110 - if (null != mediaRow)
105 + if (null != mediaTuple)
106 {
112 - mediaRow.LastSequence = lastSequence;
107 + mediaTuple.LastSequence = lastSequence;
108 }
109 }
110 }
src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs new
+453
@@ -0,0 +1,453 @@
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.Services;
16 +
17 + internal class UpdateTransformsWithFileFacades
18 + {
19 + public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions, IEnumerable<FileFacade> fileFacades)
20 + {
21 + this.Messaging = messaging;
22 + this.Output = output;
23 + this.SubStorages = subStorages;
24 + this.TableDefinitions = tableDefinitions;
25 + this.FileFacades = fileFacades;
26 + }
27 +
28 + private IMessaging Messaging { get; }
29 +
30 + private WindowsInstallerData Output { get; }
31 +
32 + private IEnumerable<SubStorage> SubStorages { get; }
33 +
34 + private TableDefinitionCollection TableDefinitions { get; }
35 +
36 + private IEnumerable<FileFacade> FileFacades { get; }
37 +
38 + public void Execute()
39 + {
40 + var fileFacadesByDiskId = new Dictionary<int, Dictionary<string, FileFacade>>();
41 +
42 + // Index patch file facades by diskId+fileId.
43 + foreach (var facade in this.FileFacades)
44 + {
45 + if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades))
46 + {
47 + mediaFacades = new Dictionary<string, FileFacade>();
48 + fileFacadesByDiskId.Add(facade.DiskId, mediaFacades);
49 + }
50 +
51 + mediaFacades.Add(facade.Id, facade);
52 + }
53 +
54 + var patchMediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]);
55 +
56 + // Index paired transforms by name without the "#" prefix.
57 + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data);
58 +
59 + // Copy File bind data into substorages
60 + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#")))
61 + {
62 + var mainTransform = substorage.Data;
63 +
64 + var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
65 +
66 + var pairedTransform = pairedTransforms["#" + substorage.Name];
67 +
68 + // Copy Media.LastSequence.
69 + var pairedMediaTable = pairedTransform.Tables["Media"];
70 + foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
71 + {
72 + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
73 + pairedMediaRow.LastSequence = patchMediaRow.LastSequence;
74 + }
75 +
76 + // Validate file row changes for keypath-related issues
77 + this.ValidateFileRowChanges(mainTransform);
78 +
79 + // Index File table of pairedTransform
80 + var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
81 +
82 + var mainFileTable = mainTransform.Tables["File"];
83 + if (null != mainFileTable)
84 + {
85 + // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
86 + mainTransform.Tables.Remove("MsiFileHash");
87 +
88 + foreach (FileRow mainFileRow in mainFileTable.Rows)
89 + {
90 + if (RowOperation.Delete == mainFileRow.Operation)
91 + {
92 + continue;
93 + }
94 + else if (RowOperation.None == mainFileRow.Operation)
95 + {
96 + continue;
97 + }
98 +
99 + // Index patch files by diskId+fileId
100 + if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades))
101 + {
102 + mediaFacades = new Dictionary<string, FileFacade>();
103 + fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades);
104 + }
105 +
106 + // copy data from the patch back to the transform
107 + if (mediaFacades.TryGetValue(mainFileRow.File, out var facade))
108 + {
109 + var patchFileRow = facade.GetFileRow();
110 + var pairedFileRow = pairedFileRows.Get(mainFileRow.File);
111 +
112 + for (var i = 0; i < patchFileRow.Fields.Length; i++)
113 + {
114 + var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty;
115 + var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty;
116 +
117 + if (1 == i)
118 + {
119 + // File.Component_ changes should not come from the shared file rows
120 + // that contain the file information as each individual transform might
121 + // have different changes (or no changes at all).
122 + }
123 + else if (6 == i) // File.Attributes should not changed for binary deltas
124 + {
125 +#if TODO_PATCHING_DELTA
126 + if (null != patchFileRow.Patch)
127 + {
128 + // File.Attribute should not change for binary deltas
129 + pairedFileRow.Attributes = mainFileRow.Attributes;
130 + mainFileRow.Fields[i].Modified = false;
131 + }
132 +#endif
133 + }
134 + else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform
135 + {
136 + // file sequence is updated in Patch table instead of File table for delta patches
137 +#if TODO_PATCHING_DELTA
138 + if (null != patchFileRow.Patch)
139 + {
140 + pairedFileRow.Fields[i].Modified = false;
141 + }
142 + else
143 +#endif
144 + {
145 + pairedFileRow[i] = patchFileRow[i];
146 + pairedFileRow.Fields[i].Modified = true;
147 + }
148 + mainFileRow.Fields[i].Modified = false;
149 + }
150 + else if (patchValue != mainValue)
151 + {
152 + mainFileRow[i] = patchFileRow[i];
153 + mainFileRow.Fields[i].Modified = true;
154 + if (mainFileRow.Operation == RowOperation.None)
155 + {
156 + mainFileRow.Operation = RowOperation.Modify;
157 + }
158 + }
159 + }
160 +
161 + // Copy MsiFileHash row for this File.
162 + if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow))
163 + {
164 + //patchHashRow = patchFileRow.Hash;
165 + throw new NotImplementedException();
166 + }
167 +
168 + if (null != patchHashRow)
169 + {
170 + var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
171 + var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
172 + for (var i = 0; i < patchHashRow.Fields.Length; i++)
173 + {
174 + mainHashRow[i] = patchHashRow[i];
175 + if (i > 1)
176 + {
177 + // assume all hash fields have been modified
178 + mainHashRow.Fields[i].Modified = true;
179 + }
180 + }
181 +
182 + // assume the MsiFileHash operation follows the File one
183 + mainHashRow.Operation = mainFileRow.Operation;
184 + }
185 +
186 + // copy MsiAssemblyName rows for this File
187 +#if TODO_PATCHING
188 + List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
189 + if (null != patchAssemblyNameRows)
190 + {
191 + var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
192 + foreach (var patchAssemblyNameRow in patchAssemblyNameRows)
193 + {
194 + // Copy if there isn't an identical modified/added row already in the transform.
195 + var foundMatchingModifiedRow = false;
196 + foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows)
197 + {
198 + if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
199 + {
200 + foundMatchingModifiedRow = true;
201 + break;
202 + }
203 + }
204 +
205 + if (!foundMatchingModifiedRow)
206 + {
207 + var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
208 + for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
209 + {
210 + mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
211 + }
212 +
213 + // assume value field has been modified
214 + mainAssemblyNameRow.Fields[2].Modified = true;
215 + mainAssemblyNameRow.Operation = mainFileRow.Operation;
216 + }
217 + }
218 + }
219 +#endif
220 +
221 + // Add patch header for this file
222 +#if TODO_PATCHING_DELTA
223 + if (null != patchFileRow.Patch)
224 + {
225 + // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
226 + this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
227 + this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
228 +
229 + // Add to Patch table
230 + var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
231 + if (0 == patchTable.Rows.Count)
232 + {
233 + patchTable.Operation = TableOperation.Add;
234 + }
235 +
236 + var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
237 + patchRow[0] = patchFileRow.File;
238 + patchRow[1] = patchFileRow.Sequence;
239 +
240 + var patchFile = new FileInfo(patchFileRow.Source);
241 + patchRow[2] = (int)patchFile.Length;
242 + patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
243 +
244 + var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
245 + if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length)
246 + {
247 + streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
248 +
249 + var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
250 + if (0 == patchHeadersTable.Rows.Count)
251 + {
252 + patchHeadersTable.Operation = TableOperation.Add;
253 + }
254 +
255 + var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
256 + patchHeadersRow[0] = streamName;
257 + patchHeadersRow[1] = patchFileRow.Patch;
258 + patchRow[5] = streamName;
259 + patchHeadersRow.Operation = RowOperation.Add;
260 + }
261 + else
262 + {
263 + patchRow[4] = patchFileRow.Patch;
264 + }
265 + patchRow.Operation = RowOperation.Add;
266 + }
267 +#endif
268 + }
269 + else
270 + {
271 + // TODO: throw because all transform rows should have made it into the patch
272 + }
273 + }
274 + }
275 +
276 + this.Output.Tables.Remove("Media");
277 + this.Output.Tables.Remove("File");
278 + this.Output.Tables.Remove("MsiFileHash");
279 + this.Output.Tables.Remove("MsiAssemblyName");
280 + }
281 + }
282 +
283 + /// <summary>
284 + /// Adds the PatchFiles action to the sequence table if it does not already exist.
285 + /// </summary>
286 + /// <param name="table">The sequence table to check or modify.</param>
287 + /// <param name="mainTransform">The primary authoring transform.</param>
288 + /// <param name="pairedTransform">The secondary patch transform.</param>
289 + /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
290 + private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow)
291 + {
292 + var tableName = table.ToString();
293 +
294 + // Find/add PatchFiles action (also determine sequence for it).
295 + // Search mainTransform first, then pairedTransform (pairedTransform overrides).
296 + var hasPatchFilesAction = false;
297 + var installFilesSequence = 0;
298 + var duplicateFilesSequence = 0;
299 +
300 + TestSequenceTableForPatchFilesAction(
301 + mainTransform.Tables[tableName],
302 + ref hasPatchFilesAction,
303 + ref installFilesSequence,
304 + ref duplicateFilesSequence);
305 + TestSequenceTableForPatchFilesAction(
306 + pairedTransform.Tables[tableName],
307 + ref hasPatchFilesAction,
308 + ref installFilesSequence,
309 + ref duplicateFilesSequence);
310 + if (!hasPatchFilesAction)
311 + {
312 + WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionTuple);
313 +
314 + var sequence = patchFilesActionTuple.Sequence;
315 +
316 + // Test for default sequence value's appropriateness
317 + if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence))
318 + {
319 + if (0 != duplicateFilesSequence)
320 + {
321 + if (duplicateFilesSequence < installFilesSequence)
322 + {
323 + throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
324 + }
325 + else
326 + {
327 + sequence = (duplicateFilesSequence + installFilesSequence) / 2;
328 + if (installFilesSequence == sequence || duplicateFilesSequence == sequence)
329 + {
330 + throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
331 + }
332 + }
333 + }
334 + else
335 + {
336 + sequence = installFilesSequence + 1;
337 + }
338 + }
339 +
340 + var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
341 + if (0 == sequenceTable.Rows.Count)
342 + {
343 + sequenceTable.Operation = TableOperation.Add;
344 + }
345 +
346 + var patchAction = sequenceTable.CreateRow(null);
347 + patchAction[0] = patchFilesActionTuple.Action;
348 + patchAction[1] = patchFilesActionTuple.Condition;
349 + patchAction[2] = sequence;
350 + patchAction.Operation = RowOperation.Add;
351 + }
352 + }
353 +
354 + /// <summary>
355 + /// Tests sequence table for PatchFiles and associated actions
356 + /// </summary>
357 + /// <param name="sequenceTable">The table to test.</param>
358 + /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
359 + /// <param name="installFilesSequence">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
360 + /// <param name="duplicateFilesSequence">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
361 + private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence)
362 + {
363 + if (null != sequenceTable)
364 + {
365 + foreach (var row in sequenceTable.Rows)
366 + {
367 + var actionName = row.FieldAsString(0);
368 + switch (actionName)
369 + {
370 + case "PatchFiles":
371 + hasPatchFilesAction = true;
372 + break;
373 +
374 + case "InstallFiles":
375 + installFilesSequence = row.FieldAsInteger(2);
376 + break;
377 +
378 + case "DuplicateFiles":
379 + duplicateFilesSequence = row.FieldAsInteger(2);
380 + break;
381 + }
382 + }
383 + }
384 + }
385 +
386 + /// <summary>
387 + /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
388 + /// </summary>
389 + /// <param name="output">The output to validate.</param>
390 + private void ValidateFileRowChanges(WindowsInstallerData transform)
391 + {
392 + var componentTable = transform.Tables["Component"];
393 + var fileTable = transform.Tables["File"];
394 +
395 + // There's no sense validating keypaths if the transform has no component or file table
396 + if (componentTable == null || fileTable == null)
397 + {
398 + return;
399 + }
400 +
401 + var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
402 +
403 + // Index the Component table for non-directory & non-registry key paths.
404 + foreach (var row in componentTable.Rows)
405 + {
406 + var keyPath = row.FieldAsString(5);
407 + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
408 + {
409 + componentKeyPath.Add(row.FieldAsString(0), keyPath);
410 + }
411 + }
412 +
413 + var componentWithChangedKeyPath = new Dictionary<string, string>();
414 + var componentWithNonKeyPathChanged = new Dictionary<string, string>();
415 + // Verify changes in the file table, now that file diffing has occurred
416 + foreach (FileRow row in fileTable.Rows)
417 + {
418 + if (RowOperation.Modify != row.Operation)
419 + {
420 + continue;
421 + }
422 +
423 + var fileId = row.FieldAsString(0);
424 + var componentId = row.FieldAsString(1);
425 +
426 + // If this file is the keypath of a component
427 + if (componentKeyPath.ContainsValue(fileId))
428 + {
429 + if (!componentWithChangedKeyPath.ContainsKey(componentId))
430 + {
431 + componentWithChangedKeyPath.Add(componentId, fileId);
432 + }
433 + }
434 + else
435 + {
436 + if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
437 + {
438 + componentWithNonKeyPathChanged.Add(componentId, fileId);
439 + }
440 + }
441 + }
442 +
443 + foreach (var componentFile in componentWithNonKeyPathChanged)
444 + {
445 + // Make sure all changes to non keypath files also had a change in the keypath.
446 + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath))
447 + {
448 + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath));
449 + }
450 + }
451 + }
452 + }
453 +}
src/WixToolset.Core/Bind/FileFacade.cs
+19 -5
@@ -15,18 +15,27 @@ namespace WixToolset.Core.Bind
15 {
16 this.FileTuple = file;
17 this.AssemblyTuple = assembly;
18 +
19 + this.Identifier = file.Id;
20 + this.ComponentRef = file.ComponentRef;
21 }
22
23 public FileFacade(bool fromModule, FileTuple file)
24 {
25 this.FromModule = fromModule;
26 this.FileTuple = file;
27 +
28 + this.Identifier = file.Id;
29 + this.ComponentRef = file.ComponentRef;
30 }
31
26 - internal FileFacade(FileRow row)
32 + public FileFacade(FileRow row)
33 {
34 this.FromTransform = true;
35 this.FileRow = row;
36 +
37 + this.Identifier = new Identifier(AccessModifier.Private, row.File);
38 + this.ComponentRef = row.Component;
39 }
40
41 public bool FromModule { get; }
@@ -39,11 +48,11 @@ namespace WixToolset.Core.Bind
48
49 private AssemblyTuple AssemblyTuple { get; }
50
42 - public string Id => this.FileRow == null ? this.FileTuple.Id.Id : this.FileRow.File;
51 + public string Id => this.Identifier.Id;
52
44 - public Identifier Identifier => this.FileRow == null ? this.FileTuple.Id : throw new NotImplementedException();
53 + public Identifier Identifier { get; }
54
46 - public string ComponentRef => this.FileRow == null ? this.FileTuple.ComponentRef : this.FileRow.Component;
55 + public string ComponentRef { get; }
56
57 public int DiskId
58 {
@@ -137,7 +146,7 @@ namespace WixToolset.Core.Bind
146 }
147 }
148
140 - public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : throw new NotImplementedException();
149 + public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : null;
150
151 public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblyTuple?.ApplicationFileRef : throw new NotImplementedException();
152
@@ -153,5 +162,10 @@ namespace WixToolset.Core.Bind
162 /// Gets or sets the MsiFileHash row for this file.
163 /// </summary>
164 public MsiFileHashTuple Hash { get; set; }
165 +
166 + /// <summary>
167 + /// Allows direct access to the underlying FileRow as requried for patching.
168 + /// </summary>
169 + public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException();
170 }
171 }
src/WixToolset.Core/CommandLine/BuildCommand.cs
+1
@@ -334,6 +334,7 @@ namespace WixToolset.Core.CommandLine
334 context.DelayedFields = resolveResult.DelayedFields;
335 context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
336 context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>();
337 + context.FileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>();
338 context.Ices = Array.Empty<string>(); // TODO: set this correctly
339 context.IntermediateFolder = intermediateFolder;
340 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
+2 -2
@@ -16,7 +16,7 @@ namespace WixToolsetTest.CoreIntegration
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")]
19 + [Fact]
20 public void CanBuildSimplePatch()
21 {
22 var folder = TestData.Get(@"TestData\PatchSingle");
@@ -45,7 +45,7 @@ namespace WixToolsetTest.CoreIntegration
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.
48 + Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray());
49 }
50 }
51