aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-09-27 15:03:47 -0700
committerRob Mensching <rob@firegiant.com>2022-09-27 16:25:20 -0700
commitdfcd6728a9d56ac37a5daa8cbedabbf10c333773 (patch)
tree95f25aa8009ce3e8eef3e86d33c35c450148299f
parent188952d84f5789128ddd32e7adf09e60899af43a (diff)
downloadwix-dfcd6728a9d56ac37a5daa8cbedabbf10c333773.tar.gz
wix-dfcd6728a9d56ac37a5daa8cbedabbf10c333773.tar.bz2
wix-dfcd6728a9d56ac37a5daa8cbedabbf10c333773.zip
Introduce PatchFilterMap to remove Row.SectionId
A Row's SectionId is not set correctly in most scenarios. It was only really needed for the old section-based patch filtering. As section-base patch filtering was replaced in favor of the more logical filter generation, Row.SectionId was archaic and mostly outdated/wrong data.
-rw-r--r--src/api/wix/WixToolset.Data/WindowsInstaller/Row.cs31
-rw-r--r--src/api/wix/WixToolset.Data/WindowsInstaller/Xsd/objects.xsd2
-rw-r--r--src/api/wix/WixToolset.Extensibility/BaseWindowsInstallerBackendBinderExtension.cs7
-rw-r--r--src/api/wix/WixToolset.Extensibility/IWindowsInstallerBackendBinderExtension.cs8
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs5
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs19
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GeneratePatchFilterIdsCommand.cs233
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs225
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs18
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchFilterMap.cs80
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs142
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Differ.cs9
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs5
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs10
14 files changed, 426 insertions, 368 deletions
diff --git a/src/api/wix/WixToolset.Data/WindowsInstaller/Row.cs b/src/api/wix/WixToolset.Data/WindowsInstaller/Row.cs
index f44082d3..cbb47492 100644
--- a/src/api/wix/WixToolset.Data/WindowsInstaller/Row.cs
+++ b/src/api/wix/WixToolset.Data/WindowsInstaller/Row.cs
@@ -53,17 +53,6 @@ namespace WixToolset.Data.WindowsInstaller
53 public RowOperation Operation { get; set; } 53 public RowOperation Operation { get; set; }
54 54
55 /// <summary> 55 /// <summary>
56 /// Gets or sets wether the row is a duplicate of another row thus redundant.
57 /// </summary>
58 public bool Redundant { get; set; }
59
60 /// <summary>
61 /// Gets or sets the SectionId property on the row.
62 /// </summary>
63 /// <value>The SectionId property on the row.</value>
64 public string SectionId { get; set; }
65
66 /// <summary>
67 /// Gets the source file and line number for the row. 56 /// Gets the source file and line number for the row.
68 /// </summary> 57 /// </summary>
69 /// <value>Source file and line number.</value> 58 /// <value>Source file and line number.</value>
@@ -276,8 +265,6 @@ namespace WixToolset.Data.WindowsInstaller
276 265
277 bool empty = reader.IsEmptyElement; 266 bool empty = reader.IsEmptyElement;
278 RowOperation operation = RowOperation.None; 267 RowOperation operation = RowOperation.None;
279 bool redundant = false;
280 string sectionId = null;
281 SourceLineNumber sourceLineNumbers = null; 268 SourceLineNumber sourceLineNumbers = null;
282 269
283 while (reader.MoveToNextAttribute()) 270 while (reader.MoveToNextAttribute())
@@ -287,12 +274,6 @@ namespace WixToolset.Data.WindowsInstaller
287 case "op": 274 case "op":
288 operation = (RowOperation)Enum.Parse(typeof(RowOperation), reader.Value, true); 275 operation = (RowOperation)Enum.Parse(typeof(RowOperation), reader.Value, true);
289 break; 276 break;
290 case "redundant":
291 redundant = reader.Value.Equals("yes");
292 break;
293 case "sectionId":
294 sectionId = reader.Value;
295 break;
296 case "sourceLineNumber": 277 case "sourceLineNumber":
297 sourceLineNumbers = SourceLineNumber.CreateFromEncoded(reader.Value); 278 sourceLineNumbers = SourceLineNumber.CreateFromEncoded(reader.Value);
298 break; 279 break;
@@ -301,8 +282,6 @@ namespace WixToolset.Data.WindowsInstaller
301 282
302 var row = table.CreateRow(sourceLineNumbers); 283 var row = table.CreateRow(sourceLineNumbers);
303 row.Operation = operation; 284 row.Operation = operation;
304 row.Redundant = redundant;
305 row.SectionId = sectionId;
306 285
307 // loop through all the fields in a row 286 // loop through all the fields in a row
308 if (!empty) 287 if (!empty)
@@ -364,16 +343,6 @@ namespace WixToolset.Data.WindowsInstaller
364 writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant()); 343 writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant());
365 } 344 }
366 345
367 if (this.Redundant)
368 {
369 writer.WriteAttributeString("redundant", "yes");
370 }
371
372 if (null != this.SectionId)
373 {
374 writer.WriteAttributeString("sectionId", this.SectionId);
375 }
376
377 if (null != this.SourceLineNumbers) 346 if (null != this.SourceLineNumbers)
378 { 347 {
379 writer.WriteAttributeString("sourceLineNumber", this.SourceLineNumbers.GetEncoded()); 348 writer.WriteAttributeString("sourceLineNumber", this.SourceLineNumbers.GetEncoded());
diff --git a/src/api/wix/WixToolset.Data/WindowsInstaller/Xsd/objects.xsd b/src/api/wix/WixToolset.Data/WindowsInstaller/Xsd/objects.xsd
index 5d95a59c..94909032 100644
--- a/src/api/wix/WixToolset.Data/WindowsInstaller/Xsd/objects.xsd
+++ b/src/api/wix/WixToolset.Data/WindowsInstaller/Xsd/objects.xsd
@@ -96,8 +96,6 @@
96 </xs:restriction> 96 </xs:restriction>
97 </xs:simpleType> 97 </xs:simpleType>
98 </xs:attribute> 98 </xs:attribute>
99 <xs:attribute name="redundant" type="YesNoType" />
100 <xs:attribute name="sectionId" type="xs:string" />
101 <xs:attribute name="sourceLineNumber" type="xs:string" /> 99 <xs:attribute name="sourceLineNumber" type="xs:string" />
102 </xs:complexType> 100 </xs:complexType>
103 </xs:element> 101 </xs:element>
diff --git a/src/api/wix/WixToolset.Extensibility/BaseWindowsInstallerBackendBinderExtension.cs b/src/api/wix/WixToolset.Extensibility/BaseWindowsInstallerBackendBinderExtension.cs
index a54f05fc..0b31cdd7 100644
--- a/src/api/wix/WixToolset.Extensibility/BaseWindowsInstallerBackendBinderExtension.cs
+++ b/src/api/wix/WixToolset.Extensibility/BaseWindowsInstallerBackendBinderExtension.cs
@@ -63,6 +63,13 @@ namespace WixToolset.Extensibility
63 } 63 }
64 64
65 /// <summary> 65 /// <summary>
66 /// See <see cref="IWindowsInstallerBackendBinderExtension.FinalizePatchFilterIds(WindowsInstallerData, IDictionary{Row, string}, string)"/>
67 /// </summary>
68 public virtual void FinalizePatchFilterIds(WindowsInstallerData data, IDictionary<Row, string> rowToFilterId, string filterIdPrefix)
69 {
70 }
71
72 /// <summary>
66 /// See <see cref="IWindowsInstallerBackendBinderExtension.PreBackendBind(IBindContext)"/> 73 /// See <see cref="IWindowsInstallerBackendBinderExtension.PreBackendBind(IBindContext)"/>
67 /// </summary> 74 /// </summary>
68 public virtual IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<IBindFileWithPath> files) 75 public virtual IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<IBindFileWithPath> files)
diff --git a/src/api/wix/WixToolset.Extensibility/IWindowsInstallerBackendBinderExtension.cs b/src/api/wix/WixToolset.Extensibility/IWindowsInstallerBackendBinderExtension.cs
index 067745c2..fdf753c7 100644
--- a/src/api/wix/WixToolset.Extensibility/IWindowsInstallerBackendBinderExtension.cs
+++ b/src/api/wix/WixToolset.Extensibility/IWindowsInstallerBackendBinderExtension.cs
@@ -30,6 +30,14 @@ namespace WixToolset.Extensibility
30 void SymbolsFinalized(IntermediateSection section); 30 void SymbolsFinalized(IntermediateSection section);
31 31
32 /// <summary> 32 /// <summary>
33 /// Extension can process the filter ids applied to rows when processing patches.
34 /// </summary>
35 /// <param name="data">The <c>WindowsInstallerData</c> with rows to apply filters to.</param>
36 /// <param name="rowToFilterId">The mapping that applies a filter id to a row.</param>
37 /// <param name="filterIdPrefix">The prefix to use applying additional filters to rows.</param>
38 void FinalizePatchFilterIds(WindowsInstallerData data, IDictionary<Row, string> rowToFilterId, string filterIdPrefix);
39
40 /// <summary>
33 /// Finds an existing cabinet that contains the provided files. 41 /// Finds an existing cabinet that contains the provided files.
34 /// </summary> 42 /// </summary>
35 /// <param name="cabinetPath">Path to the cabinet.</param> 43 /// <param name="cabinetPath">Path to the cabinet.</param>
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs
index 1bed65d5..89fc81db 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs
@@ -71,11 +71,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
71 71
72 foreach (Row row in table.Rows) 72 foreach (Row row in table.Rows)
73 { 73 {
74 if (row.Redundant)
75 {
76 continue;
77 }
78
79 string rowString = this.RowToIdtDefinition(row, keepAddedColumns); 74 string rowString = this.RowToIdtDefinition(row, keepAddedColumns);
80 byte[] rowBytes; 75 byte[] rowBytes;
81 76
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
index 5cdafe7e..0d88cfd1 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
@@ -16,13 +16,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
16 16
17 internal class CreatePatchTransformsCommand 17 internal class CreatePatchTransformsCommand
18 { 18 {
19 public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, IFileResolver fileResolver, IReadOnlyCollection<IResolverExtension> resolverExtensions, Intermediate intermediate, string intermediateFolder, IReadOnlyCollection<IBindPath> bindPaths) 19 public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, IFileResolver fileResolver, IReadOnlyCollection<IResolverExtension> resolverExtensions, IReadOnlyCollection<IWindowsInstallerBackendBinderExtension> backendExtensions, Intermediate intermediate, string intermediateFolder, IReadOnlyCollection<IBindPath> bindPaths)
20 { 20 {
21 this.Messaging = messaging; 21 this.Messaging = messaging;
22 this.BackendHelper = backendHelper; 22 this.BackendHelper = backendHelper;
23 this.PathResolver = pathResolver; 23 this.PathResolver = pathResolver;
24 this.FileResolver = fileResolver; 24 this.FileResolver = fileResolver;
25 this.ResolverExtensions = resolverExtensions; 25 this.ResolverExtensions = resolverExtensions;
26 this.BackendExtensions = backendExtensions;
26 this.Intermediate = intermediate; 27 this.Intermediate = intermediate;
27 this.IntermediateFolder = intermediateFolder; 28 this.IntermediateFolder = intermediateFolder;
28 this.BindPaths = bindPaths; 29 this.BindPaths = bindPaths;
@@ -38,16 +39,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
38 39
39 private IReadOnlyCollection<IResolverExtension> ResolverExtensions { get; } 40 private IReadOnlyCollection<IResolverExtension> ResolverExtensions { get; }
40 41
42 private IReadOnlyCollection<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
43
41 private Intermediate Intermediate { get; } 44 private Intermediate Intermediate { get; }
42 45
43 private string IntermediateFolder { get; } 46 private string IntermediateFolder { get; }
44 47
45 private IReadOnlyCollection<IBindPath> BindPaths { get; } 48 private IReadOnlyCollection<IBindPath> BindPaths { get; }
46 49
50 public PatchFilterMap PatchFilterMap { get; private set; }
51
47 public IEnumerable<PatchTransform> PatchTransforms { get; private set; } 52 public IEnumerable<PatchTransform> PatchTransforms { get; private set; }
48 53
49 public IEnumerable<PatchTransform> Execute() 54 public IEnumerable<PatchTransform> Execute()
50 { 55 {
56 var patchFilterMap = new PatchFilterMap();
51 var patchTransforms = new List<PatchTransform>(); 57 var patchTransforms = new List<PatchTransform>();
52 58
53 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols); 59 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols);
@@ -63,19 +69,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
63 69
64 if (patchRefSymbols.Count > 0) 70 if (patchRefSymbols.Count > 0)
65 { 71 {
66 var targetCommand = new GenerateSectionIdsCommand(targetData); 72 var targetCommand = new GeneratePatchFilterIdsCommand(this.BackendExtensions, targetData, "target:");
67 targetCommand.Execute(); 73 targetCommand.Execute();
68 74
69 var updatedCommand = new GenerateSectionIdsCommand(updatedData); 75 patchFilterMap.AddTargetRowFilterIds(targetCommand.RowToFilterId);
76
77 var updatedCommand = new GeneratePatchFilterIdsCommand(this.BackendExtensions, updatedData, "updated:");
70 updatedCommand.Execute(); 78 updatedCommand.Execute();
79
80 patchFilterMap.AddUpdatedRowFilterIds(updatedCommand.RowToFilterId);
71 } 81 }
72 82
73 var command = new GenerateTransformCommand(this.Messaging, targetData, updatedData, preserveUnchangedRows: true, showPedanticMessages: false); 83 var command = new GenerateTransformCommand(this.Messaging, targetData, updatedData, patchFilterMap, preserveUnchangedRows: true, showPedanticMessages: false);
74 var transform = command.Execute(); 84 var transform = command.Execute();
75 85
76 patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); 86 patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform));
77 } 87 }
78 88
89 this.PatchFilterMap = patchFilterMap;
79 this.PatchTransforms = patchTransforms; 90 this.PatchTransforms = patchTransforms;
80 91
81 return this.PatchTransforms; 92 return this.PatchTransforms;
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GeneratePatchFilterIdsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GeneratePatchFilterIdsCommand.cs
new file mode 100644
index 00000000..caddc5fa
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GeneratePatchFilterIdsCommand.cs
@@ -0,0 +1,233 @@
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 WixToolset.Data.WindowsInstaller;
9 using WixToolset.Extensibility;
10
11 /// <summary>
12 /// Creates section ids on rows which form logical groupings of resources.
13 /// </summary>
14 internal class GeneratePatchFilterIdsCommand
15 {
16 public GeneratePatchFilterIdsCommand(IReadOnlyCollection<IWindowsInstallerBackendBinderExtension> backendExtensions, WindowsInstallerData data, string filterIdPrefix)
17 {
18 this.BackendExtensions = backendExtensions;
19 this.Data = data;
20 this.FilterIdPrefix = filterIdPrefix;
21 }
22
23 private IReadOnlyCollection<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
24
25 private WindowsInstallerData Data { get; }
26
27 private string FilterIdPrefix { get; }
28
29 public IDictionary<Row, string> RowToFilterId { get; private set; }
30
31 public void Execute()
32 {
33 this.RowToFilterId = new Dictionary<Row, string>();
34
35 var output = this.Data;
36
37 // First assign and index section ids for the tables that are in their own sections.
38 this.AssignFilterIdsToTable(output.Tables["Binary"], 0);
39 var componentSectionIdIndex = this.AssignFilterIdsToTable(output.Tables["Component"], 0);
40 var customActionSectionIdIndex = this.AssignFilterIdsToTable(output.Tables["CustomAction"], 0);
41 this.AssignFilterIdsToTable(output.Tables["Directory"], 0);
42 var featureSectionIdIndex = this.AssignFilterIdsToTable(output.Tables["Feature"], 0);
43 this.AssignFilterIdsToTable(output.Tables["Icon"], 0);
44 var digitalCertificateSectionIdIndex = this.AssignFilterIdsToTable(output.Tables["MsiDigitalCertificate"], 0);
45 this.AssignFilterIdsToTable(output.Tables["Property"], 0);
46
47 // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here.
48 var fileFilterIdIndex = this.ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0);
49 var appIdFilterIdIndex = this.ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5);
50 var odbcDataSourceFilterIdIndex = this.ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0);
51 var odbcDriverSectionIdIndex = this.ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0);
52 var registrySectionIdIndex = this.ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0);
53 var serviceInstallSectionIdIndex = this.ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0);
54
55 // Now handle all the tables which only rely on previous indexes and order does not matter.
56 foreach (var table in output.Tables)
57 {
58 switch (table.Name)
59 {
60 case "MsiFileHash":
61 this.ConnectTableToFilterId(table, fileFilterIdIndex, 0);
62 break;
63 case "MsiAssembly":
64 case "MsiAssemblyName":
65 this.ConnectTableToFilterId(table, componentSectionIdIndex, 0);
66 break;
67 case "MsiPackageCertificate":
68 case "MsiPatchCertificate":
69 this.ConnectTableToFilterId(table, digitalCertificateSectionIdIndex, 1);
70 break;
71 case "CreateFolder":
72 case "FeatureComponents":
73 case "MoveFile":
74 case "ReserveCost":
75 case "ODBCTranslator":
76 this.ConnectTableToFilterId(table, componentSectionIdIndex, 1);
77 break;
78 case "TypeLib":
79 this.ConnectTableToFilterId(table, componentSectionIdIndex, 2);
80 break;
81 case "Shortcut":
82 case "Environment":
83 this.ConnectTableToFilterId(table, componentSectionIdIndex, 3);
84 break;
85 case "RemoveRegistry":
86 this.ConnectTableToFilterId(table, componentSectionIdIndex, 4);
87 break;
88 case "ServiceControl":
89 this.ConnectTableToFilterId(table, componentSectionIdIndex, 5);
90 break;
91 case "IniFile":
92 case "RemoveIniFile":
93 this.ConnectTableToFilterId(table, componentSectionIdIndex, 7);
94 break;
95 case "AppId":
96 this.ConnectTableToFilterId(table, appIdFilterIdIndex, 0);
97 break;
98 case "Condition":
99 this.ConnectTableToFilterId(table, featureSectionIdIndex, 0);
100 break;
101 case "ODBCSourceAttribute":
102 this.ConnectTableToFilterId(table, odbcDataSourceFilterIdIndex, 0);
103 break;
104 case "ODBCAttribute":
105 this.ConnectTableToFilterId(table, odbcDriverSectionIdIndex, 0);
106 break;
107 case "AdminExecuteSequence":
108 case "AdminUISequence":
109 case "AdvtExecuteSequence":
110 case "AdvtUISequence":
111 case "InstallExecuteSequence":
112 case "InstallUISequence":
113 this.ConnectTableToFilterId(table, customActionSectionIdIndex, 0);
114 break;
115 case "LockPermissions":
116 case "MsiLockPermissions":
117 foreach (var row in table.Rows)
118 {
119 var lockObject = row.FieldAsString(0);
120 var tableName = row.FieldAsString(1);
121
122 var filterId = String.Empty;
123 switch (tableName)
124 {
125 case "File":
126 filterId = fileFilterIdIndex[lockObject];
127 break;
128 case "Registry":
129 filterId = registrySectionIdIndex[lockObject];
130 break;
131 case "ServiceInstall":
132 filterId = serviceInstallSectionIdIndex[lockObject];
133 break;
134 }
135
136 if (!String.IsNullOrEmpty(filterId))
137 {
138 this.RowToFilterId.Add(row, filterId);
139 }
140 }
141 break;
142 }
143 }
144
145 // Now pass the data to each backend extension to allow them to analyze the data and determine their proper filter ids.
146 foreach (var extension in this.BackendExtensions)
147 {
148 extension.FinalizePatchFilterIds(this.Data, this.RowToFilterId, this.FilterIdPrefix);
149 }
150 }
151
152 private Dictionary<string, string> AssignFilterIdsToTable(Table table, int rowPrimaryKeyIndex)
153 {
154 var primaryKeyToFilterId = new Dictionary<string, string>();
155
156 if (null != table)
157 {
158 foreach (var row in table.Rows)
159 {
160 var filterId = this.GetNewFilterId(row);
161
162 this.RowToFilterId.Add(row, filterId);
163
164 primaryKeyToFilterId.Add(row.FieldAsString(rowPrimaryKeyIndex), filterId);
165 }
166 }
167
168 return primaryKeyToFilterId;
169 }
170
171 /// <summary>
172 /// Connects a table's rows to an already sectioned table.
173 /// </summary>
174 /// <param name="table">The table containing rows that need to be connected to sections.</param>
175 /// <param name="filterIdByPrimaryKey">A hashtable containing keys to map table to its section.</param>
176 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
177 private void ConnectTableToFilterId(Table table, Dictionary<string, string> filterIdByPrimaryKey, int rowIndex)
178 {
179 if (null != table)
180 {
181 foreach (var row in table.Rows)
182 {
183 if (filterIdByPrimaryKey.TryGetValue(row.FieldAsString(rowIndex), out var filterId))
184 {
185 this.RowToFilterId.Add(row, filterId);
186 }
187 }
188 }
189 }
190
191 /// <summary>
192 /// Connects a table's rows to a table with filter ids already assigned and produces an index for other tables to connect to it.
193 /// </summary>
194 /// <param name="table">The table containing rows that need to be connected to sections.</param>
195 /// <param name="filterIdsByPrimaryKey">A dictionary containing keys to map table to its section.</param>
196 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
197 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
198 /// <returns>A dictionary containing the tables key for each row paired with its assigned section id.</returns>
199 private Dictionary<string, string> ConnectTableToSectionAndIndex(Table table, Dictionary<string, string> filterIdsByPrimaryKey, int rowIndex, int rowPrimaryKeyIndex)
200 {
201 var newPrimaryKeyToSectionId = new Dictionary<string, string>();
202
203 if (null != table)
204 {
205 foreach (var row in table.Rows)
206 {
207 var foreignKey = row.FieldAsString(rowIndex);
208
209 if (!filterIdsByPrimaryKey.TryGetValue(foreignKey, out var filterId))
210 {
211 continue;
212 }
213
214 this.RowToFilterId.Add(row, filterId);
215
216 var primaryKey = row.FieldAsString(rowPrimaryKeyIndex);
217
218 if (!String.IsNullOrEmpty(primaryKey) && filterIdsByPrimaryKey.ContainsKey(primaryKey))
219 {
220 newPrimaryKeyToSectionId.Add(primaryKey, filterId);
221 }
222 }
223 }
224
225 return newPrimaryKeyToSectionId;
226 }
227
228 private string GetNewFilterId(Row row)
229 {
230 return this.FilterIdPrefix + row.Number.ToString(CultureInfo.InvariantCulture);
231 }
232 }
233}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs
deleted file mode 100644
index c7bebbed..00000000
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs
+++ /dev/null
@@ -1,225 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using WixToolset.Data.WindowsInstaller;
9
10 /// <summary>
11 /// Creates section ids on rows which form logical groupings of resources.
12 /// </summary>
13 internal class GenerateSectionIdsCommand
14 {
15 private int sectionCount;
16
17 public GenerateSectionIdsCommand(WindowsInstallerData data)
18 {
19 this.Data = data;
20 }
21
22 private WindowsInstallerData Data { get; }
23
24 public void Execute()
25 {
26 var output = this.Data;
27
28 this.sectionCount = 0;
29
30 // First assign and index section ids for the tables that are in their own sections.
31 this.AssignSectionIdsToTable(output.Tables["Binary"], 0);
32 var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0);
33 var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0);
34 this.AssignSectionIdsToTable(output.Tables["Directory"], 0);
35 var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0);
36 this.AssignSectionIdsToTable(output.Tables["Icon"], 0);
37 var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0);
38 this.AssignSectionIdsToTable(output.Tables["Property"], 0);
39
40 // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here.
41 var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0);
42 var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5);
43 var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0);
44 var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0);
45 var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0);
46 var serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0);
47
48 // Now handle all the tables which only rely on previous indexes and order does not matter.
49 foreach (var table in output.Tables)
50 {
51 switch (table.Name)
52 {
53 case "MsiFileHash":
54 ConnectTableToSection(table, fileSectionIdIndex, 0);
55 break;
56 case "MsiAssembly":
57 case "MsiAssemblyName":
58 ConnectTableToSection(table, componentSectionIdIndex, 0);
59 break;
60 case "MsiPackageCertificate":
61 case "MsiPatchCertificate":
62 ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1);
63 break;
64 case "CreateFolder":
65 case "FeatureComponents":
66 case "MoveFile":
67 case "ReserveCost":
68 case "ODBCTranslator":
69 ConnectTableToSection(table, componentSectionIdIndex, 1);
70 break;
71 case "TypeLib":
72 ConnectTableToSection(table, componentSectionIdIndex, 2);
73 break;
74 case "Shortcut":
75 case "Environment":
76 ConnectTableToSection(table, componentSectionIdIndex, 3);
77 break;
78 case "RemoveRegistry":
79 ConnectTableToSection(table, componentSectionIdIndex, 4);
80 break;
81 case "ServiceControl":
82 ConnectTableToSection(table, componentSectionIdIndex, 5);
83 break;
84 case "IniFile":
85 case "RemoveIniFile":
86 ConnectTableToSection(table, componentSectionIdIndex, 7);
87 break;
88 case "AppId":
89 ConnectTableToSection(table, appIdSectionIdIndex, 0);
90 break;
91 case "Condition":
92 ConnectTableToSection(table, featureSectionIdIndex, 0);
93 break;
94 case "ODBCSourceAttribute":
95 ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0);
96 break;
97 case "ODBCAttribute":
98 ConnectTableToSection(table, odbcDriverSectionIdIndex, 0);
99 break;
100 case "AdminExecuteSequence":
101 case "AdminUISequence":
102 case "AdvtExecuteSequence":
103 case "AdvtUISequence":
104 case "InstallExecuteSequence":
105 case "InstallUISequence":
106 ConnectTableToSection(table, customActionSectionIdIndex, 0);
107 break;
108 case "LockPermissions":
109 case "MsiLockPermissions":
110 foreach (var row in table.Rows)
111 {
112 var lockObject = row.FieldAsString(0);
113 var tableName = row.FieldAsString(1);
114 switch (tableName)
115 {
116 case "File":
117 row.SectionId = fileSectionIdIndex[lockObject];
118 break;
119 case "Registry":
120 row.SectionId = registrySectionIdIndex[lockObject];
121 break;
122 case "ServiceInstall":
123 row.SectionId = serviceInstallSectionIdIndex[lockObject];
124 break;
125 }
126 }
127 break;
128 }
129 }
130
131 // Now pass the output to each unbinder extension to allow them to analyze the output and determine their proper section ids.
132 //foreach (IUnbinderExtension extension in this.unbinderExtensions)
133 //{
134 // extension.GenerateSectionIds(output);
135 //}
136 }
137
138 /// <summary>
139 /// Creates new section ids on all the rows in a table.
140 /// </summary>
141 /// <param name="table">The table to add sections to.</param>
142 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
143 /// <returns>A dictionary containing the tables key for each row paired with its assigned section id.</returns>
144 private Dictionary<string, string> AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex)
145 {
146 var primaryKeyToSectionId = new Dictionary<string, string>();
147
148 if (null != table)
149 {
150 foreach (var row in table.Rows)
151 {
152 row.SectionId = this.GetNewSectionId();
153
154 primaryKeyToSectionId.Add(row.FieldAsString(rowPrimaryKeyIndex), row.SectionId);
155 }
156 }
157
158 return primaryKeyToSectionId;
159 }
160
161 /// <summary>
162 /// Connects a table's rows to an already sectioned table.
163 /// </summary>
164 /// <param name="table">The table containing rows that need to be connected to sections.</param>
165 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param>
166 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
167 private static void ConnectTableToSection(Table table, Dictionary<string, string> sectionIdIndex, int rowIndex)
168 {
169 if (null != table)
170 {
171 foreach (var row in table.Rows)
172 {
173 if (sectionIdIndex.TryGetValue(row.FieldAsString(rowIndex), out var sectionId))
174 {
175 row.SectionId = sectionId;
176 }
177 }
178 }
179 }
180
181 /// <summary>
182 /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it.
183 /// </summary>
184 /// <param name="table">The table containing rows that need to be connected to sections.</param>
185 /// <param name="sectionIdIndex">A dictionary containing keys to map table to its section.</param>
186 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
187 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
188 /// <returns>A dictionary containing the tables key for each row paired with its assigned section id.</returns>
189 private static Dictionary<string, string> ConnectTableToSectionAndIndex(Table table, Dictionary<string, string> sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex)
190 {
191 var newPrimaryKeyToSectionId = new Dictionary<string, string>();
192
193 if (null != table)
194 {
195 foreach (var row in table.Rows)
196 {
197 var foreignKey = row.FieldAsString(rowIndex);
198
199 if (!sectionIdIndex.TryGetValue(foreignKey, out var sectionId))
200 {
201 continue;
202 }
203
204 row.SectionId = sectionId;
205
206 var primaryKey = row.FieldAsString(rowPrimaryKeyIndex);
207
208 if (!String.IsNullOrEmpty(primaryKey) && sectionIdIndex.ContainsKey(primaryKey))
209 {
210 newPrimaryKeyToSectionId.Add(primaryKey, row.SectionId);
211 }
212 }
213 }
214
215 return newPrimaryKeyToSectionId;
216 }
217
218 private string GetNewSectionId()
219 {
220 this.sectionCount++;
221
222 return "wix.section." + this.sectionCount.ToString(CultureInfo.InvariantCulture);
223 }
224 }
225}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
index 92a0e11f..4efc6a11 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
@@ -6,6 +6,7 @@ namespace WixToolset.Core.WindowsInstaller
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Globalization; 7 using System.Globalization;
8 using WixToolset.Core.Native.Msi; 8 using WixToolset.Core.Native.Msi;
9 using WixToolset.Core.WindowsInstaller.Bind;
9 using WixToolset.Data; 10 using WixToolset.Data;
10 using WixToolset.Data.Symbols; 11 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller; 12 using WixToolset.Data.WindowsInstaller;
@@ -16,18 +17,18 @@ namespace WixToolset.Core.WindowsInstaller
16 /// </summary> 17 /// </summary>
17 internal class GenerateTransformCommand 18 internal class GenerateTransformCommand
18 { 19 {
19 private const char SectionDelimiter = '/';
20 private readonly IMessaging messaging; 20 private readonly IMessaging messaging;
21 private SummaryInformationStreams transformSummaryInfo; 21 private SummaryInformationStreams transformSummaryInfo;
22 22
23 /// <summary> 23 /// <summary>
24 /// Instantiates a new Differ class. 24 /// Instantiates a new Differ class.
25 /// </summary> 25 /// </summary>
26 public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages) 26 public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, PatchFilterMap patchFilterMap, bool preserveUnchangedRows, bool showPedanticMessages)
27 { 27 {
28 this.messaging = messaging; 28 this.messaging = messaging;
29 this.TargetOutput = targetOutput; 29 this.TargetOutput = targetOutput;
30 this.UpdatedOutput = updatedOutput; 30 this.UpdatedOutput = updatedOutput;
31 this.PatchFilterMap = patchFilterMap;
31 this.PreserveUnchangedRows = preserveUnchangedRows; 32 this.PreserveUnchangedRows = preserveUnchangedRows;
32 this.ShowPedanticMessages = showPedanticMessages; 33 this.ShowPedanticMessages = showPedanticMessages;
33 } 34 }
@@ -36,6 +37,8 @@ namespace WixToolset.Core.WindowsInstaller
36 37
37 private WindowsInstallerData UpdatedOutput { get; } 38 private WindowsInstallerData UpdatedOutput { get; }
38 39
40 public PatchFilterMap PatchFilterMap { get; }
41
39 private TransformFlags ValidationFlags { get; } 42 private TransformFlags ValidationFlags { get; }
40 43
41 private bool ShowPedanticMessages { get; } 44 private bool ShowPedanticMessages { get; }
@@ -112,7 +115,6 @@ namespace WixToolset.Core.WindowsInstaller
112 foreach (var updatedRow in updatedTable.Rows) 115 foreach (var updatedRow in updatedTable.Rows)
113 { 116 {
114 updatedRow.Operation = RowOperation.Add; 117 updatedRow.Operation = RowOperation.Add;
115 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
116 addedTable.Rows.Add(updatedRow); 118 addedTable.Rows.Add(updatedRow);
117 } 119 }
118 } 120 }
@@ -177,7 +179,6 @@ namespace WixToolset.Core.WindowsInstaller
177 else if (null == updatedRow) 179 else if (null == updatedRow)
178 { 180 {
179 targetRow.Operation = RowOperation.Delete; 181 targetRow.Operation = RowOperation.Delete;
180 targetRow.SectionId += SectionDelimiter;
181 182
182 comparedRow = targetRow; 183 comparedRow = targetRow;
183 keepRow = true; 184 keepRow = true;
@@ -189,9 +190,10 @@ namespace WixToolset.Core.WindowsInstaller
189 if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) 190 if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
190 { 191 {
191 // Include only summary information rows that are allowed in a transform. 192 // Include only summary information rows that are allowed in a transform.
192 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) 193 if (Enum.IsDefined(typeof(SummaryInformation.Transform), updatedRow.FieldAsInteger(0)))
193 { 194 {
194 updatedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId; 195 this.PatchFilterMap.AddTargetRowFilterToUpdatedRowFilter(targetRow, updatedRow);
196
195 comparedRow = updatedRow; 197 comparedRow = updatedRow;
196 keepRow = true; 198 keepRow = true;
197 } 199 }
@@ -273,8 +275,9 @@ namespace WixToolset.Core.WindowsInstaller
273 275
274 if (keepRow) 276 if (keepRow)
275 { 277 {
278 this.PatchFilterMap.AddTargetRowFilterToUpdatedRowFilter(targetRow, updatedRow);
279
276 comparedRow = updatedRow; 280 comparedRow = updatedRow;
277 comparedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId;
278 } 281 }
279 } 282 }
280 } 283 }
@@ -340,7 +343,6 @@ namespace WixToolset.Core.WindowsInstaller
340 var updatedRow = updatedPrimaryKeyEntry.Value; 343 var updatedRow = updatedPrimaryKeyEntry.Value;
341 344
342 updatedRow.Operation = RowOperation.Add; 345 updatedRow.Operation = RowOperation.Add;
343 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
344 rows.Add(updatedRow); 346 rows.Add(updatedRow);
345 } 347 }
346 } 348 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchFilterMap.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchFilterMap.cs
new file mode 100644
index 00000000..4822f3a5
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchFilterMap.cs
@@ -0,0 +1,80 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data.WindowsInstaller;
8
9 internal class PatchFilterMap
10 {
11 private readonly Dictionary<Row, PatchFilter> filterMap = new Dictionary<Row, PatchFilter>();
12
13 public void AddTargetRowFilterIds(IEnumerable<KeyValuePair<Row, string>> rowFilterIds)
14 {
15 foreach (var kvp in rowFilterIds)
16 {
17 this.filterMap.Add(kvp.Key, new PatchFilter(kvp.Key, kvp.Value, null));
18 }
19 }
20
21 public void AddUpdatedRowFilterIds(IEnumerable<KeyValuePair<Row, string>> rowFilterIds)
22 {
23 foreach (var kvp in rowFilterIds)
24 {
25 this.filterMap.Add(kvp.Key, new PatchFilter(kvp.Key, null, kvp.Value));
26 }
27 }
28
29 public void AddTargetRowFilterToUpdatedRowFilter(Row targetRow, Row updatedRow)
30 {
31 if (this.filterMap.TryGetValue(targetRow, out var targetPatchFilter) && !String.IsNullOrEmpty(targetPatchFilter.TargetFilterId))
32 {
33 // If the updated row didn't have a patch filter, it gets one now because the target patch has
34 // a target filter id to add.
35 if (!this.filterMap.TryGetValue(updatedRow, out var updatedPatchFilter))
36 {
37 updatedPatchFilter = new PatchFilter(updatedRow, null, null);
38 this.filterMap.Add(updatedRow, updatedPatchFilter);
39 }
40
41 updatedPatchFilter.SetTargetFilterId(targetPatchFilter);
42 }
43 }
44
45 internal bool ContainsPatchFilterForRow(Row row)
46 {
47 return this.filterMap.ContainsKey(row);
48 }
49
50 internal bool TryGetPatchFiltersForRow(Row row, out string targetFilterId, out string updatedFilterId)
51 {
52 this.filterMap.TryGetValue(row, out var patchFilter);
53
54 targetFilterId = patchFilter?.TargetFilterId;
55 updatedFilterId = patchFilter?.UpdatedFilterId;
56 return patchFilter != null;
57 }
58
59 private class PatchFilter
60 {
61 public PatchFilter(Row row, string targetFilterId, string updatedFilterId)
62 {
63 this.Row = row;
64 this.TargetFilterId = targetFilterId;
65 this.UpdatedFilterId = updatedFilterId;
66 }
67
68 public Row Row { get; }
69
70 public string TargetFilterId { get; private set; }
71
72 public string UpdatedFilterId { get; }
73
74 public void SetTargetFilterId(PatchFilter targetPatchFilter)
75 {
76 this.TargetFilterId = targetPatchFilter.TargetFilterId;
77 }
78 }
79 }
80}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs
index 4966a0b4..e7d93660 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs
@@ -11,18 +11,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
11 11
12 internal class ReduceTransformCommand 12 internal class ReduceTransformCommand
13 { 13 {
14 private const char SectionDelimiter = '/'; 14 public ReduceTransformCommand(Intermediate intermediate, IEnumerable<PatchTransform> patchTransforms, PatchFilterMap patchFilterMap)
15
16 public ReduceTransformCommand(Intermediate intermediate, IEnumerable<PatchTransform> patchTransforms)
17 { 15 {
18 this.Intermediate = intermediate; 16 this.Intermediate = intermediate;
19 this.PatchTransforms = patchTransforms; 17 this.PatchTransforms = patchTransforms;
18 this.PatchFilterMap = patchFilterMap;
20 } 19 }
21 20
22 private Intermediate Intermediate { get; } 21 private Intermediate Intermediate { get; }
23 22
24 private IEnumerable<PatchTransform> PatchTransforms { get; } 23 private IEnumerable<PatchTransform> PatchTransforms { get; }
25 24
25 private PatchFilterMap PatchFilterMap { get; }
26
26 public void Execute() 27 public void Execute()
27 { 28 {
28 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList(); 29 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList();
@@ -51,8 +52,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
51 private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefSymbol> patchRefSymbols) 52 private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefSymbol> patchRefSymbols)
52 { 53 {
53 // identify sections to keep 54 // identify sections to keep
54 var oldSections = new Dictionary<string, Row>(); 55 var targetFilterIdsToKeep = new Dictionary<string, Row>();
55 var newSections = new Dictionary<string, Row>(); 56 var updatedFilterIdsToKeep = new Dictionary<string, Row>();
56 var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>(); 57 var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>();
57 var sequenceList = new List<Table>(); 58 var sequenceList = new List<Table>();
58 var componentFeatureAddsIndex = new Dictionary<string, List<string>>(); 59 var componentFeatureAddsIndex = new Dictionary<string, List<string>>();
@@ -72,10 +73,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
72 foreach (var patchRefSymbol in patchRefSymbols) 73 foreach (var patchRefSymbol in patchRefSymbols)
73 { 74 {
74 var tableName = patchRefSymbol.Table; 75 var tableName = patchRefSymbol.Table;
75 var key = patchRefSymbol.PrimaryKeys; 76 var primaryKey = patchRefSymbol.PrimaryKeys;
76 77
77 // Short circuit filtering if all changes should be included. 78 // Short circuit filtering if all changes should be included.
78 if ("*" == tableName && "*" == key) 79 if ("*" == tableName && "*" == primaryKey)
79 { 80 {
80 RemoveProductCodeFromTransform(transform); 81 RemoveProductCodeFromTransform(transform);
81 return true; 82 return true;
@@ -88,22 +89,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
88 } 89 }
89 90
90 // Index the table. 91 // Index the table.
91 if (!tableKeyRows.TryGetValue(tableName, out var keyRows)) 92 if (!tableKeyRows.TryGetValue(tableName, out var rowsByPrimaryKey))
92 { 93 {
93 keyRows = table.Rows.ToDictionary(r => r.GetPrimaryKey()); 94 rowsByPrimaryKey = table.Rows.ToDictionary(r => r.GetPrimaryKey());
94 tableKeyRows.Add(tableName, keyRows); 95 tableKeyRows.Add(tableName, rowsByPrimaryKey);
95 } 96 }
96 97
97 if (!keyRows.TryGetValue(key, out var row)) 98 if (!rowsByPrimaryKey.TryGetValue(primaryKey, out var row))
98 { 99 {
99 // Row not found. 100 // Row not found.
100 continue; 101 continue;
101 } 102 }
102 103
103 // Differ.sectionDelimiter 104 // Differ.sectionDelimiter
104 var sections = row.SectionId.Split(SectionDelimiter); 105 if (this.PatchFilterMap.TryGetPatchFiltersForRow(row, out var targetFilterId, out var updatedFilterId))
105 oldSections[sections[0]] = row; 106 {
106 newSections[sections[1]] = row; 107 targetFilterIdsToKeep[targetFilterId] = row;
108 updatedFilterIdsToKeep[updatedFilterId] = row;
109 }
107 } 110 }
108 111
109 // throw away sections not referenced 112 // throw away sections not referenced
@@ -221,49 +224,34 @@ namespace WixToolset.Core.WindowsInstaller.Bind
221 } 224 }
222 } 225 }
223 226
224 if (null == row.SectionId) 227 if (this.IsInPatchFamily(row, targetFilterIdsToKeep, updatedFilterIdsToKeep))
225 { 228 {
226 table.Rows.RemoveAt(i); 229 if ("Component" == table.Name)
227 i--;
228 }
229 else
230 {
231 var sections = row.SectionId.Split(SectionDelimiter);
232 // ignore the row without section id.
233 if (0 == sections[0].Length && 0 == sections[1].Length)
234 { 230 {
235 table.Rows.RemoveAt(i); 231 keptComponents.Add(row.FieldAsString(0), row);
236 i--;
237 } 232 }
238 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
239 {
240 if ("Component" == table.Name)
241 {
242 keptComponents.Add(row.FieldAsString(0), row);
243 }
244 233
245 if ("Directory" == table.Name) 234 if ("Directory" == table.Name)
246 { 235 {
247 keptDirectories.Add(row.FieldAsString(0), row); 236 keptDirectories.Add(row.FieldAsString(0), row);
248 }
249
250 if ("Feature" == table.Name)
251 {
252 keptFeatures.Add(row.FieldAsString(0), row);
253 }
254
255 keptRows++;
256 } 237 }
257 else 238
239 if ("Feature" == table.Name)
258 { 240 {
259 table.Rows.RemoveAt(i); 241 keptFeatures.Add(row.FieldAsString(0), row);
260 i--;
261 } 242 }
243
244 keptRows++;
245 }
246 else
247 {
248 table.Rows.RemoveAt(i);
249 i--;
262 } 250 }
263 } 251 }
264 } 252 }
265 253
266 keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); 254 keptRows += ReduceTransformSequenceTable(sequenceList, targetFilterIdsToKeep, updatedFilterIdsToKeep, customActionTable);
267 255
268 if (null != directoryTable) 256 if (null != directoryTable)
269 { 257 {
@@ -345,7 +333,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
345 } 333 }
346 } 334 }
347 335
348 keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); 336 keptRows += ReduceTransformSequenceTable(sequenceList, targetFilterIdsToKeep, updatedFilterIdsToKeep, customActionTable);
349 337
350 // Delete tables that are empty. 338 // Delete tables that are empty.
351 var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList(); 339 var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList();
@@ -358,6 +346,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
358 return keptRows > 0; 346 return keptRows > 0;
359 } 347 }
360 348
349 private bool IsInPatchFamily(Row row, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections)
350 {
351 var result = false;
352
353 if (this.PatchFilterMap.TryGetPatchFiltersForRow(row, out var targetFilterId, out var updatedFilterId))
354 {
355 if ((String.IsNullOrEmpty(targetFilterId) && newSections.ContainsKey(updatedFilterId)) || (String.IsNullOrEmpty(updatedFilterId) && oldSections.ContainsKey(targetFilterId)))
356 {
357 result = true;
358 }
359 else if (!String.IsNullOrEmpty(targetFilterId) && !String.IsNullOrEmpty(updatedFilterId) && (oldSections.ContainsKey(targetFilterId) || newSections.ContainsKey(updatedFilterId)))
360 {
361 result = true;
362 }
363 }
364
365 return result;
366 }
367
361 /// <summary> 368 /// <summary>
362 /// Check if the section is in a PatchFamily. 369 /// Check if the section is in a PatchFamily.
363 /// </summary> 370 /// </summary>
@@ -415,7 +422,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
415 /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param> 422 /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param>
416 /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param> 423 /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param>
417 /// <returns>Number of rows left</returns> 424 /// <returns>Number of rows left</returns>
418 private static int ReduceTransformSequenceTable(List<Table> sequenceList, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections, Dictionary<string, Row> customAction) 425 private int ReduceTransformSequenceTable(List<Table> sequenceList, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections, Dictionary<string, Row> customAction)
419 { 426 {
420 var keptRows = 0; 427 var keptRows = 0;
421 428
@@ -424,19 +431,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
424 for (var i = 0; i < currentTable.Rows.Count; i++) 431 for (var i = 0; i < currentTable.Rows.Count; i++)
425 { 432 {
426 var row = currentTable.Rows[i]; 433 var row = currentTable.Rows[i];
427 var actionName = row.Fields[0].Data.ToString(); 434 var actionName = row.FieldAsString(0);
428 var sections = row.SectionId.Split(SectionDelimiter);
429 var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0);
430 435
431 if (row.Operation == RowOperation.None) 436 if (row.Operation == RowOperation.None)
432 { 437 {
433 // Ignore the rows without section id. 438 if (this.IsInPatchFamily(row, oldSections, newSections))
434 if (isSectionIdEmpty)
435 {
436 currentTable.Rows.RemoveAt(i);
437 i--;
438 }
439 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
440 { 439 {
441 keptRows++; 440 keptRows++;
442 } 441 }
@@ -457,12 +456,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
457 } 456 }
458 else if (!sequenceChanged && conditionChanged) 457 else if (!sequenceChanged && conditionChanged)
459 { 458 {
460 if (isSectionIdEmpty) 459 if (this.IsInPatchFamily(row, oldSections, newSections))
461 {
462 currentTable.Rows.RemoveAt(i);
463 i--;
464 }
465 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
466 { 460 {
467 keptRows++; 461 keptRows++;
468 } 462 }
@@ -474,12 +468,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
474 } 468 }
475 else if (sequenceChanged && conditionChanged) 469 else if (sequenceChanged && conditionChanged)
476 { 470 {
477 if (isSectionIdEmpty) 471 if (this.IsInPatchFamily(row, oldSections, newSections))
478 {
479 row.Fields[1].Modified = false;
480 keptRows++;
481 }
482 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
483 { 472 {
484 keptRows++; 473 keptRows++;
485 } 474 }
@@ -492,13 +481,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
492 } 481 }
493 else if (row.Operation == RowOperation.Delete) 482 else if (row.Operation == RowOperation.Delete)
494 { 483 {
495 if (isSectionIdEmpty) 484 if (this.IsInPatchFamily(row, oldSections, newSections))
496 {
497 // it is a stardard action which is added by wix, we should keep this action.
498 row.Operation = RowOperation.None;
499 keptRows++;
500 }
501 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
502 { 485 {
503 keptRows++; 486 keptRows++;
504 } 487 }
@@ -519,11 +502,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
519 } 502 }
520 else if (row.Operation == RowOperation.Add) 503 else if (row.Operation == RowOperation.Add)
521 { 504 {
522 if (isSectionIdEmpty) 505 // Keep unfiltered added rows.
506 if (!this.PatchFilterMap.ContainsPatchFilterForRow(row))
523 { 507 {
524 keptRows++; 508 keptRows++;
525 } 509 }
526 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) 510 else if (this.IsInPatchFamily(row, oldSections, newSections))
527 { 511 {
528 keptRows++; 512 keptRows++;
529 } 513 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs
index f4e4a1fc..e4cfe22d 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs
@@ -17,7 +17,6 @@ namespace WixToolset.Core.WindowsInstaller
17 /// </summary> 17 /// </summary>
18 public sealed class Differ 18 public sealed class Differ
19 { 19 {
20 private const char SectionDelimiter = '/';
21 private readonly IMessaging messaging; 20 private readonly IMessaging messaging;
22 private SummaryInformationStreams transformSummaryInfo; 21 private SummaryInformationStreams transformSummaryInfo;
23 22
@@ -111,7 +110,6 @@ namespace WixToolset.Core.WindowsInstaller
111 foreach (var updatedRow in updatedTable.Rows) 110 foreach (var updatedRow in updatedTable.Rows)
112 { 111 {
113 updatedRow.Operation = RowOperation.Add; 112 updatedRow.Operation = RowOperation.Add;
114 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
115 addedTable.Rows.Add(updatedRow); 113 addedTable.Rows.Add(updatedRow);
116 } 114 }
117 } 115 }
@@ -200,7 +198,6 @@ namespace WixToolset.Core.WindowsInstaller
200 else if (null == updatedRow) 198 else if (null == updatedRow)
201 { 199 {
202 operation = targetRow.Operation = RowOperation.Delete; 200 operation = targetRow.Operation = RowOperation.Delete;
203 targetRow.SectionId += SectionDelimiter;
204 comparedRow = targetRow; 201 comparedRow = targetRow;
205 keepRow = true; 202 keepRow = true;
206 } 203 }
@@ -211,9 +208,8 @@ namespace WixToolset.Core.WindowsInstaller
211 if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) 208 if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
212 { 209 {
213 // ignore rows that shouldn't be in a transform 210 // ignore rows that shouldn't be in a transform
214 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) 211 if (Enum.IsDefined(typeof(SummaryInformation.Transform), updatedRow.FieldAsInteger(0)))
215 { 212 {
216 updatedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId;
217 comparedRow = updatedRow; 213 comparedRow = updatedRow;
218 keepRow = true; 214 keepRow = true;
219 operation = RowOperation.Modify; 215 operation = RowOperation.Modify;
@@ -297,7 +293,7 @@ namespace WixToolset.Core.WindowsInstaller
297 if (keepRow) 293 if (keepRow)
298 { 294 {
299 comparedRow = updatedRow; 295 comparedRow = updatedRow;
300 comparedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId; 296 //comparedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId;
301 } 297 }
302 } 298 }
303 } 299 }
@@ -361,7 +357,6 @@ namespace WixToolset.Core.WindowsInstaller
361 var updatedRow = (Row)updatedPrimaryKeyEntry.Value; 357 var updatedRow = (Row)updatedPrimaryKeyEntry.Value;
362 358
363 updatedRow.Operation = RowOperation.Add; 359 updatedRow.Operation = RowOperation.Add;
364 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
365 rows.Add(updatedRow); 360 rows.Add(updatedRow);
366 } 361 }
367 } 362 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
index f372af82..4ade5b1d 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
@@ -141,10 +141,7 @@ namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices
141 { 141 {
142 var table = data.EnsureTable(tableDefinition); 142 var table = data.EnsureTable(tableDefinition);
143 143
144 var row = table.CreateRow(symbol.SourceLineNumbers); 144 return table.CreateRow(symbol.SourceLineNumbers);
145 row.SectionId = section.Id;
146
147 return row;
148 } 145 }
149 146
150 public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions) 147 public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions)
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
index d1d7c19b..ace382de 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
@@ -34,14 +34,18 @@ namespace WixToolset.Core.WindowsInstaller
34 34
35 // Create transforms named in patch transforms. 35 // Create transforms named in patch transforms.
36 IEnumerable<PatchTransform> patchTransforms; 36 IEnumerable<PatchTransform> patchTransforms;
37 PatchFilterMap patchFilterMap;
37 { 38 {
38 var command = new CreatePatchTransformsCommand(messaging, backendHelper, pathResolver, fileResolver, resolveExtensions, context.IntermediateRepresentation, context.IntermediateFolder, context.BindPaths); 39 var command = new CreatePatchTransformsCommand(messaging, backendHelper, pathResolver, fileResolver, resolveExtensions, backendExtensions, context.IntermediateRepresentation, context.IntermediateFolder, context.BindPaths);
39 patchTransforms = command.Execute(); 40 command.Execute();
41
42 patchTransforms = command.PatchTransforms;
43 patchFilterMap = command.PatchFilterMap;
40 } 44 }
41 45
42 // Reduce transforms. 46 // Reduce transforms.
43 { 47 {
44 var command = new ReduceTransformCommand(context.IntermediateRepresentation, patchTransforms); 48 var command = new ReduceTransformCommand(context.IntermediateRepresentation, patchTransforms, patchFilterMap);
45 command.Execute(); 49 command.Execute();
46 } 50 }
47 51