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