main
cs 532 lines 22.4 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.WindowsInstaller.Bind
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11
12 internal class ReduceTransformCommand
13 {
14 public ReduceTransformCommand(Intermediate intermediate, IEnumerable<PatchTransform> patchTransforms, PatchFilterMap patchFilterMap)
15 {
16 this.Intermediate = intermediate;
17 this.PatchTransforms = patchTransforms;
18 this.PatchFilterMap = patchFilterMap;
19 }
20
21 private Intermediate Intermediate { get; }
22
23 private IEnumerable<PatchTransform> PatchTransforms { get; }
24
25 private PatchFilterMap PatchFilterMap { get; }
26
27 public void Execute()
28 {
29 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList();
30
31 var patchRefSymbols = symbols.OfType<WixPatchRefSymbol>().ToList();
32
33 if (patchRefSymbols.Count > 0)
34 {
35 foreach (var patchTransform in this.PatchTransforms)
36 {
37 if (!this.ReduceTransform(patchTransform.Transform, patchRefSymbols))
38 {
39 // transform has none of the content authored into this patch
40 continue;
41 }
42 }
43 }
44 }
45
46 /// <summary>
47 /// Reduce the transform according to the patch references.
48 /// </summary>
49 /// <param name="transform">transform generated by torch.</param>
50 /// <param name="patchRefSymbols">Table contains patch family filter.</param>
51 /// <returns>true if the transform is not empty</returns>
52 private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefSymbol> patchRefSymbols)
53 {
54 // identify sections to keep
55 var targetFilterIdsToKeep = new Dictionary<string, Row>();
56 var updatedFilterIdsToKeep = new Dictionary<string, Row>();
57 var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>();
58 var sequenceList = new List<Table>();
59 var componentFeatureAddsIndex = new Dictionary<string, List<string>>();
60 var customActionTable = new Dictionary<string, Row>();
61 var directoryTableAdds = new Dictionary<string, Row>();
62 var featureTableAdds = new Dictionary<string, Row>();
63 var keptComponents = new Dictionary<string, Row>();
64 var keptDirectories = new Dictionary<string, Row>();
65 var keptFeatures = new Dictionary<string, Row>();
66 var keptLockPermissions = new HashSet<string>();
67 var keptMsiLockPermissionExs = new HashSet<string>();
68
69 var componentCreateFolderIndex = new Dictionary<string, List<string>>();
70 var directoryLockPermissionsIndex = new Dictionary<string, List<Row>>();
71 var directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>();
72
73 foreach (var patchRefSymbol in patchRefSymbols)
74 {
75 var tableName = patchRefSymbol.Table;
76 var primaryKey = patchRefSymbol.PrimaryKeys;
77
78 // Short circuit filtering if all changes should be included.
79 if ("*" == tableName && "*" == primaryKey)
80 {
81 RemoveProductCodeFromTransform(transform);
82 return true;
83 }
84
85 if (!transform.Tables.TryGetTable(tableName, out var table))
86 {
87 // Table not found.
88 continue;
89 }
90
91 // Index the table.
92 if (!tableKeyRows.TryGetValue(tableName, out var rowsByPrimaryKey))
93 {
94 rowsByPrimaryKey = table.Rows.ToDictionary(r => r.GetPrimaryKey());
95 tableKeyRows.Add(tableName, rowsByPrimaryKey);
96 }
97
98 if (!rowsByPrimaryKey.TryGetValue(primaryKey, out var row))
99 {
100 // Row not found.
101 continue;
102 }
103
104 if (this.PatchFilterMap.TryGetPatchFiltersForRow(row, out var targetFilterId, out var updatedFilterId))
105 {
106 targetFilterIdsToKeep[targetFilterId ?? String.Empty] = row;
107 updatedFilterIdsToKeep[updatedFilterId ?? String.Empty] = row;
108 }
109 }
110
111 // throw away sections not referenced
112 var keptRows = 0;
113 Table directoryTable = null;
114 Table featureTable = null;
115 Table lockPermissionsTable = null;
116 Table msiLockPermissionsTable = null;
117
118 foreach (var table in transform.Tables)
119 {
120 if ("_SummaryInformation" == table.Name)
121 {
122 continue;
123 }
124
125 if (table.Name == "AdminExecuteSequence"
126 || table.Name == "AdminUISequence"
127 || table.Name == "AdvtExecuteSequence"
128 || table.Name == "InstallUISequence"
129 || table.Name == "InstallExecuteSequence")
130 {
131 sequenceList.Add(table);
132 continue;
133 }
134
135 for (var i = 0; i < table.Rows.Count; i++)
136 {
137 var row = table.Rows[i];
138
139 if (table.Name == "CreateFolder")
140 {
141 var createFolderComponentId = row.FieldAsString(1);
142
143 if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList))
144 {
145 directoryList = new List<string>();
146 componentCreateFolderIndex.Add(createFolderComponentId, directoryList);
147 }
148
149 directoryList.Add(row.FieldAsString(0));
150 }
151
152 if (table.Name == "CustomAction")
153 {
154 customActionTable.Add(row.FieldAsString(0), row);
155 }
156
157 if (table.Name == "Directory")
158 {
159 directoryTable = table;
160 if (RowOperation.Add == row.Operation)
161 {
162 directoryTableAdds.Add(row.FieldAsString(0), row);
163 }
164 }
165
166 if (table.Name == "Feature")
167 {
168 featureTable = table;
169 if (RowOperation.Add == row.Operation)
170 {
171 featureTableAdds.Add(row.FieldAsString(0), row);
172 }
173 }
174
175 if (table.Name == "FeatureComponents")
176 {
177 if (RowOperation.Add == row.Operation)
178 {
179 var featureId = row.FieldAsString(0);
180 var componentId = row.FieldAsString(1);
181
182 if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList))
183 {
184 featureList = new List<string>();
185 componentFeatureAddsIndex.Add(componentId, featureList);
186 }
187
188 featureList.Add(featureId);
189 }
190 }
191
192 if (table.Name == "LockPermissions")
193 {
194 lockPermissionsTable = table;
195 if ("CreateFolder" == row.FieldAsString(1))
196 {
197 var directoryId = row.FieldAsString(0);
198
199 if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList))
200 {
201 rowList = new List<Row>();
202 directoryLockPermissionsIndex.Add(directoryId, rowList);
203 }
204
205 rowList.Add(row);
206 }
207 }
208
209 if (table.Name == "MsiLockPermissionsEx")
210 {
211 msiLockPermissionsTable = table;
212 if ("CreateFolder" == row.FieldAsString(1))
213 {
214 var directoryId = row.FieldAsString(0);
215
216 if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList))
217 {
218 rowList = new List<Row>();
219 directoryMsiLockPermissionsExIndex.Add(directoryId, rowList);
220 }
221
222 rowList.Add(row);
223 }
224 }
225
226 if (this.IsInPatchFamily(row, targetFilterIdsToKeep, updatedFilterIdsToKeep))
227 {
228 if ("Component" == table.Name)
229 {
230 keptComponents.Add(row.FieldAsString(0), row);
231 }
232
233 if ("Directory" == table.Name)
234 {
235 keptDirectories.Add(row.FieldAsString(0), row);
236 }
237
238 if ("Feature" == table.Name)
239 {
240 keptFeatures.Add(row.FieldAsString(0), row);
241 }
242
243 keptRows++;
244 }
245 else
246 {
247 table.Rows.RemoveAt(i);
248 i--;
249 }
250 }
251 }
252
253 keptRows += this.ReduceTransformSequenceTable(sequenceList, targetFilterIdsToKeep, updatedFilterIdsToKeep, customActionTable);
254
255 if (null != directoryTable)
256 {
257 foreach (var componentRow in keptComponents.Values)
258 {
259 var componentId = componentRow.FieldAsString(0);
260
261 if (RowOperation.Add == componentRow.Operation)
262 {
263 // Make sure each added component has its required directory and feature heirarchy.
264 var directoryId = componentRow.FieldAsString(2);
265 while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow))
266 {
267 if (!keptDirectories.ContainsKey(directoryId))
268 {
269 directoryTable.Rows.Add(directoryRow);
270 keptDirectories.Add(directoryId, directoryRow);
271 keptRows++;
272 }
273
274 directoryId = directoryRow.FieldAsString(1);
275 }
276
277 if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds))
278 {
279 foreach (var featureId in componentFeatureIds)
280 {
281 var currentFeatureId = featureId;
282 while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow))
283 {
284 if (!keptFeatures.ContainsKey(currentFeatureId))
285 {
286 featureTable.Rows.Add(featureRow);
287 keptFeatures.Add(currentFeatureId, featureRow);
288 keptRows++;
289 }
290
291 currentFeatureId = featureRow.FieldAsString(1);
292 }
293 }
294 }
295 }
296
297 // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept.
298 foreach (var keptComponentId in keptComponents.Keys)
299 {
300 if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList))
301 {
302 foreach (var directoryId in directoryList)
303 {
304 if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList))
305 {
306 foreach (var lockPermissionsRow in lockPermissionsRowList)
307 {
308 var key = lockPermissionsRow.GetPrimaryKey('/');
309 if (keptLockPermissions.Add(key))
310 {
311 lockPermissionsTable.Rows.Add(lockPermissionsRow);
312 keptRows++;
313 }
314 }
315 }
316
317 if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList))
318 {
319 foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList)
320 {
321 var key = msiLockPermissionsExRow.GetPrimaryKey('/');
322 if (keptMsiLockPermissionExs.Add(key))
323 {
324 msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow);
325 keptRows++;
326 }
327 }
328 }
329 }
330 }
331 }
332 }
333 }
334
335 keptRows += this.ReduceTransformSequenceTable(sequenceList, targetFilterIdsToKeep, updatedFilterIdsToKeep, customActionTable);
336
337 // Delete tables that are empty.
338 var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList();
339
340 foreach (var tableName in tablesToDelete)
341 {
342 transform.Tables.Remove(tableName);
343 }
344
345 return keptRows > 0;
346 }
347
348 private bool IsInPatchFamily(Row row, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections)
349 {
350 var result = false;
351
352 if (this.PatchFilterMap.TryGetPatchFiltersForRow(row, out var targetFilterId, out var updatedFilterId))
353 {
354 if ((String.IsNullOrEmpty(targetFilterId) && newSections.ContainsKey(updatedFilterId)) || (String.IsNullOrEmpty(updatedFilterId) && oldSections.ContainsKey(targetFilterId)))
355 {
356 result = true;
357 }
358 else if (!String.IsNullOrEmpty(targetFilterId) && !String.IsNullOrEmpty(updatedFilterId) && (oldSections.ContainsKey(targetFilterId) || newSections.ContainsKey(updatedFilterId)))
359 {
360 result = true;
361 }
362 }
363
364 return result;
365 }
366
367 /// <summary>
368 /// Check if the section is in a PatchFamily.
369 /// </summary>
370 /// <param name="oldSection">Section id in target wixout</param>
371 /// <param name="newSection">Section id in upgrade wixout</param>
372 /// <param name="oldSections">Dictionary contains section id should be kept in the baseline wixout.</param>
373 /// <param name="newSections">Dictionary contains section id should be kept in the upgrade wixout.</param>
374 /// <returns>true if section in patch family</returns>
375 private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections)
376 {
377 var result = false;
378
379 if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection)))
380 {
381 result = true;
382 }
383 else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection)))
384 {
385 result = true;
386 }
387
388 return result;
389 }
390
391 /// <summary>
392 /// Remove the ProductCode property from the transform.
393 /// </summary>
394 /// <param name="transform">The transform.</param>
395 /// <remarks>
396 /// Changing the ProductCode is not supported in a patch.
397 /// </remarks>
398 private static void RemoveProductCodeFromTransform(WindowsInstallerData transform)
399 {
400 if (transform.Tables.TryGetTable("Property", out var propertyTable))
401 {
402 for (var i = 0; i < propertyTable.Rows.Count; ++i)
403 {
404 var propertyRow = propertyTable.Rows[i];
405 var property = propertyRow.FieldAsString(0);
406
407 if ("ProductCode" == property)
408 {
409 propertyTable.Rows.RemoveAt(i);
410 break;
411 }
412 }
413 }
414 }
415
416 /// <summary>
417 /// Reduce the transform sequence tables.
418 /// </summary>
419 /// <param name="sequenceList">ArrayList of tables to be reduced</param>
420 /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param>
421 /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param>
422 /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param>
423 /// <returns>Number of rows left</returns>
424 private int ReduceTransformSequenceTable(List<Table> sequenceList, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections, Dictionary<string, Row> customAction)
425 {
426 var keptRows = 0;
427
428 foreach (var currentTable in sequenceList)
429 {
430 for (var i = 0; i < currentTable.Rows.Count; i++)
431 {
432 var row = currentTable.Rows[i];
433 var actionName = row.FieldAsString(0);
434
435 if (row.Operation == RowOperation.None)
436 {
437 if (this.IsInPatchFamily(row, oldSections, newSections))
438 {
439 keptRows++;
440 }
441 else
442 {
443 currentTable.Rows.RemoveAt(i);
444 i--;
445 }
446 }
447 else if (row.Operation == RowOperation.Modify)
448 {
449 var sequenceChanged = row.Fields[2].Modified;
450 var conditionChanged = row.Fields[1].Modified;
451
452 if (sequenceChanged && !conditionChanged)
453 {
454 keptRows++;
455 }
456 else if (!sequenceChanged && conditionChanged)
457 {
458 if (this.IsInPatchFamily(row, oldSections, newSections))
459 {
460 keptRows++;
461 }
462 else
463 {
464 currentTable.Rows.RemoveAt(i);
465 i--;
466 }
467 }
468 else if (sequenceChanged && conditionChanged)
469 {
470 if (this.IsInPatchFamily(row, oldSections, newSections))
471 {
472 keptRows++;
473 }
474 else
475 {
476 row.Fields[1].Modified = false;
477 keptRows++;
478 }
479 }
480 }
481 else if (row.Operation == RowOperation.Delete)
482 {
483 if (this.IsInPatchFamily(row, oldSections, newSections))
484 {
485 keptRows++;
486 }
487 else
488 {
489 if (customAction.ContainsKey(actionName))
490 {
491 currentTable.Rows.RemoveAt(i);
492 i--;
493 }
494 else
495 {
496 // it is a stardard action, we should keep this action.
497 row.Operation = RowOperation.None;
498 keptRows++;
499 }
500 }
501 }
502 else if (row.Operation == RowOperation.Add)
503 {
504 // Keep unfiltered added rows.
505 if (!this.PatchFilterMap.ContainsPatchFilterForRow(row))
506 {
507 keptRows++;
508 }
509 else if (this.IsInPatchFamily(row, oldSections, newSections))
510 {
511 keptRows++;
512 }
513 else
514 {
515 if (customAction.ContainsKey(actionName))
516 {
517 currentTable.Rows.RemoveAt(i);
518 i--;
519 }
520 else
521 {
522 keptRows++;
523 }
524 }
525 }
526 }
527 }
528
529 return keptRows;
530 }
531 }
532 }