Refactor patch filtering on the path towards making it work properly
Rob Mensching committed
Sep 27, 2022 at 01:44 UTC
28a1a91cd2c20486276ee37dacce623ed8a75d6e
9 files changed
+843
-744
src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchSubStoragesCommand.cs
renamed
+4
-525
@@ -17,7 +17,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
17
/// <summary>
18
/// Include transforms in a patch.
19
/// </summary>
20
- internal class AttachPatchTransformsCommand
20
+ internal class CreatePatchSubStoragesCommand
21
{
22
private static readonly string[] PatchUninstallBreakingTables = new[]
23
{
@@ -55,7 +55,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
55
56
private readonly TableDefinitionCollection tableDefinitions;
57
58
- public AttachPatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable<PatchTransform> transforms)
58
+ public CreatePatchSubStoragesCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable<PatchTransform> transforms)
59
{
60
this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
61
this.Messaging = messaging;
@@ -126,22 +126,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
126
127
foreach (var mainTransform in this.Transforms)
128
{
129
- var baselineSymbol = baselineSymbolsById[mainTransform.Baseline];
130
-
131
- var patchRefSymbols = symbols.OfType<WixPatchRefSymbol>().ToList();
132
- if (patchRefSymbols.Count > 0)
133
- {
134
- if (!this.ReduceTransform(mainTransform.Transform, patchRefSymbols))
135
- {
136
- // transform has none of the content authored into this patch
137
- continue;
138
- }
139
- }
140
-
129
// Validate the transform doesn't break any patch specific rules.
130
this.Validate(mainTransform);
131
132
// ensure consistent File.Sequence within each Media
133
+ var baselineSymbol = baselineSymbolsById[mainTransform.Baseline];
134
var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId];
135
136
// Ensure that files are sequenced after the last file in any transform.
@@ -383,331 +372,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
372
return success;
373
}
374
386
- /// <summary>
387
- /// Reduce the transform according to the patch references.
388
- /// </summary>
389
- /// <param name="transform">transform generated by torch.</param>
390
- /// <param name="patchRefSymbols">Table contains patch family filter.</param>
391
- /// <returns>true if the transform is not empty</returns>
392
- private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefSymbol> patchRefSymbols)
393
- {
394
- // identify sections to keep
395
- var oldSections = new Dictionary<string, Row>();
396
- var newSections = new Dictionary<string, Row>();
397
- var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>();
398
- var sequenceList = new List<Table>();
399
- var componentFeatureAddsIndex = new Dictionary<string, List<string>>();
400
- var customActionTable = new Dictionary<string, Row>();
401
- var directoryTableAdds = new Dictionary<string, Row>();
402
- var featureTableAdds = new Dictionary<string, Row>();
403
- var keptComponents = new Dictionary<string, Row>();
404
- var keptDirectories = new Dictionary<string, Row>();
405
- var keptFeatures = new Dictionary<string, Row>();
406
- var keptLockPermissions = new HashSet<string>();
407
- var keptMsiLockPermissionExs = new HashSet<string>();
408
-
409
- var componentCreateFolderIndex = new Dictionary<string, List<string>>();
410
- var directoryLockPermissionsIndex = new Dictionary<string, List<Row>>();
411
- var directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>();
412
-
413
- foreach (var patchRefSymbol in patchRefSymbols)
414
- {
415
- var tableName = patchRefSymbol.Table;
416
- var key = patchRefSymbol.PrimaryKeys;
417
-
418
- // Short circuit filtering if all changes should be included.
419
- if ("*" == tableName && "*" == key)
420
- {
421
- RemoveProductCodeFromTransform(transform);
422
- return true;
423
- }
424
-
425
- if (!transform.Tables.TryGetTable(tableName, out var table))
426
- {
427
- // Table not found.
428
- continue;
429
- }
430
-
431
- // Index the table.
432
- if (!tableKeyRows.TryGetValue(tableName, out var keyRows))
433
- {
434
- keyRows = new Dictionary<string, Row>();
435
- tableKeyRows.Add(tableName, keyRows);
436
-
437
- foreach (var newRow in table.Rows)
438
- {
439
- var primaryKey = newRow.GetPrimaryKey();
440
- keyRows.Add(primaryKey, newRow);
441
- }
442
- }
443
-
444
- if (!keyRows.TryGetValue(key, out var row))
445
- {
446
- // Row not found.
447
- continue;
448
- }
449
-
450
- // Differ.sectionDelimiter
451
- var sections = row.SectionId.Split('/');
452
- oldSections[sections[0]] = row;
453
- newSections[sections[1]] = row;
454
- }
455
-
456
- // throw away sections not referenced
457
- var keptRows = 0;
458
- Table directoryTable = null;
459
- Table featureTable = null;
460
- Table lockPermissionsTable = null;
461
- Table msiLockPermissionsTable = null;
462
-
463
- foreach (var table in transform.Tables)
464
- {
465
- if ("_SummaryInformation" == table.Name)
466
- {
467
- continue;
468
- }
469
-
470
- if (table.Name == "AdminExecuteSequence"
471
- || table.Name == "AdminUISequence"
472
- || table.Name == "AdvtExecuteSequence"
473
- || table.Name == "InstallUISequence"
474
- || table.Name == "InstallExecuteSequence")
475
- {
476
- sequenceList.Add(table);
477
- continue;
478
- }
479
-
480
- for (var i = 0; i < table.Rows.Count; i++)
481
- {
482
- var row = table.Rows[i];
483
-
484
- if (table.Name == "CreateFolder")
485
- {
486
- var createFolderComponentId = row.FieldAsString(1);
487
-
488
- if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList))
489
- {
490
- directoryList = new List<string>();
491
- componentCreateFolderIndex.Add(createFolderComponentId, directoryList);
492
- }
493
-
494
- directoryList.Add(row.FieldAsString(0));
495
- }
496
-
497
- if (table.Name == "CustomAction")
498
- {
499
- customActionTable.Add(row.FieldAsString(0), row);
500
- }
501
-
502
- if (table.Name == "Directory")
503
- {
504
- directoryTable = table;
505
- if (RowOperation.Add == row.Operation)
506
- {
507
- directoryTableAdds.Add(row.FieldAsString(0), row);
508
- }
509
- }
510
-
511
- if (table.Name == "Feature")
512
- {
513
- featureTable = table;
514
- if (RowOperation.Add == row.Operation)
515
- {
516
- featureTableAdds.Add(row.FieldAsString(0), row);
517
- }
518
- }
519
-
520
- if (table.Name == "FeatureComponents")
521
- {
522
- if (RowOperation.Add == row.Operation)
523
- {
524
- var featureId = row.FieldAsString(0);
525
- var componentId = row.FieldAsString(1);
526
-
527
- if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList))
528
- {
529
- featureList = new List<string>();
530
- componentFeatureAddsIndex.Add(componentId, featureList);
531
- }
532
-
533
- featureList.Add(featureId);
534
- }
535
- }
536
-
537
- if (table.Name == "LockPermissions")
538
- {
539
- lockPermissionsTable = table;
540
- if ("CreateFolder" == row.FieldAsString(1))
541
- {
542
- var directoryId = row.FieldAsString(0);
543
-
544
- if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList))
545
- {
546
- rowList = new List<Row>();
547
- directoryLockPermissionsIndex.Add(directoryId, rowList);
548
- }
549
-
550
- rowList.Add(row);
551
- }
552
- }
553
-
554
- if (table.Name == "MsiLockPermissionsEx")
555
- {
556
- msiLockPermissionsTable = table;
557
- if ("CreateFolder" == row.FieldAsString(1))
558
- {
559
- var directoryId = row.FieldAsString(0);
560
-
561
- if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList))
562
- {
563
- rowList = new List<Row>();
564
- directoryMsiLockPermissionsExIndex.Add(directoryId, rowList);
565
- }
566
-
567
- rowList.Add(row);
568
- }
569
- }
570
-
571
- if (null == row.SectionId)
572
- {
573
- table.Rows.RemoveAt(i);
574
- i--;
575
- }
576
- else
577
- {
578
- var sections = row.SectionId.Split('/');
579
- // ignore the row without section id.
580
- if (0 == sections[0].Length && 0 == sections[1].Length)
581
- {
582
- table.Rows.RemoveAt(i);
583
- i--;
584
- }
585
- else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
586
- {
587
- if ("Component" == table.Name)
588
- {
589
- keptComponents.Add(row.FieldAsString(0), row);
590
- }
591
-
592
- if ("Directory" == table.Name)
593
- {
594
- keptDirectories.Add(row.FieldAsString(0), row);
595
- }
596
-
597
- if ("Feature" == table.Name)
598
- {
599
- keptFeatures.Add(row.FieldAsString(0), row);
600
- }
601
-
602
- keptRows++;
603
- }
604
- else
605
- {
606
- table.Rows.RemoveAt(i);
607
- i--;
608
- }
609
- }
610
- }
611
- }
612
-
613
- keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
614
-
615
- if (null != directoryTable)
616
- {
617
- foreach (var componentRow in keptComponents.Values)
618
- {
619
- var componentId = componentRow.FieldAsString(0);
620
-
621
- if (RowOperation.Add == componentRow.Operation)
622
- {
623
- // Make sure each added component has its required directory and feature heirarchy.
624
- var directoryId = componentRow.FieldAsString(2);
625
- while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow))
626
- {
627
- if (!keptDirectories.ContainsKey(directoryId))
628
- {
629
- directoryTable.Rows.Add(directoryRow);
630
- keptDirectories.Add(directoryId, directoryRow);
631
- keptRows++;
632
- }
633
-
634
- directoryId = directoryRow.FieldAsString(1);
635
- }
636
-
637
- if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds))
638
- {
639
- foreach (var featureId in componentFeatureIds)
640
- {
641
- var currentFeatureId = featureId;
642
- while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow))
643
- {
644
- if (!keptFeatures.ContainsKey(currentFeatureId))
645
- {
646
- featureTable.Rows.Add(featureRow);
647
- keptFeatures.Add(currentFeatureId, featureRow);
648
- keptRows++;
649
- }
650
-
651
- currentFeatureId = featureRow.FieldAsString(1);
652
- }
653
- }
654
- }
655
- }
656
-
657
- // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept.
658
- foreach (var keptComponentId in keptComponents.Keys)
659
- {
660
- if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList))
661
- {
662
- foreach (var directoryId in directoryList)
663
- {
664
- if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList))
665
- {
666
- foreach (var lockPermissionsRow in lockPermissionsRowList)
667
- {
668
- var key = lockPermissionsRow.GetPrimaryKey('/');
669
- if (keptLockPermissions.Add(key))
670
- {
671
- lockPermissionsTable.Rows.Add(lockPermissionsRow);
672
- keptRows++;
673
- }
674
- }
675
- }
676
-
677
- if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList))
678
- {
679
- foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList)
680
- {
681
- var key = msiLockPermissionsExRow.GetPrimaryKey('/');
682
- if (keptMsiLockPermissionExs.Add(key))
683
- {
684
- msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow);
685
- keptRows++;
686
- }
687
- }
688
- }
689
- }
690
- }
691
- }
692
- }
693
- }
694
-
695
- keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
696
-
697
- // Delete tables that are empty.
698
- var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList();
699
-
700
- foreach (var tableName in tablesToDelete)
701
- {
702
- transform.Tables.Remove(tableName);
703
- }
704
-
705
- return keptRows > 0;
706
- }
707
-
375
private void Validate(PatchTransform patchTransform)
376
{
710
- var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it?
377
+ var transformPath = patchTransform.Baseline;
378
var transform = patchTransform.Transform;
379
380
// Changing the ProdocutCode in a patch transform is not recommended.
@@ -907,194 +574,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
574
}
575
}
576
910
- /// <summary>
911
- /// Remove the ProductCode property from the transform.
912
- /// </summary>
913
- /// <param name="transform">The transform.</param>
914
- /// <remarks>
915
- /// Changing the ProductCode is not supported in a patch.
916
- /// </remarks>
917
- private static void RemoveProductCodeFromTransform(WindowsInstallerData transform)
918
- {
919
- if (transform.Tables.TryGetTable("Property", out var propertyTable))
920
- {
921
- for (var i = 0; i < propertyTable.Rows.Count; ++i)
922
- {
923
- var propertyRow = propertyTable.Rows[i];
924
- var property = propertyRow.FieldAsString(0);
925
-
926
- if ("ProductCode" == property)
927
- {
928
- propertyTable.Rows.RemoveAt(i);
929
- break;
930
- }
931
- }
932
- }
933
- }
934
-
935
- /// <summary>
936
- /// Check if the section is in a PatchFamily.
937
- /// </summary>
938
- /// <param name="oldSection">Section id in target wixout</param>
939
- /// <param name="newSection">Section id in upgrade wixout</param>
940
- /// <param name="oldSections">Dictionary contains section id should be kept in the baseline wixout.</param>
941
- /// <param name="newSections">Dictionary contains section id should be kept in the upgrade wixout.</param>
942
- /// <returns>true if section in patch family</returns>
943
- private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections)
944
- {
945
- var result = false;
946
-
947
- if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection)))
948
- {
949
- result = true;
950
- }
951
- else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection)))
952
- {
953
- result = true;
954
- }
955
-
956
- return result;
957
- }
958
-
959
- /// <summary>
960
- /// Reduce the transform sequence tables.
961
- /// </summary>
962
- /// <param name="sequenceList">ArrayList of tables to be reduced</param>
963
- /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param>
964
- /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param>
965
- /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param>
966
- /// <returns>Number of rows left</returns>
967
- private static int ReduceTransformSequenceTable(List<Table> sequenceList, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections, Dictionary<string, Row> customAction)
968
- {
969
- var keptRows = 0;
970
-
971
- foreach (var currentTable in sequenceList)
972
- {
973
- for (var i = 0; i < currentTable.Rows.Count; i++)
974
- {
975
- var row = currentTable.Rows[i];
976
- var actionName = row.Fields[0].Data.ToString();
977
- var sections = row.SectionId.Split('/');
978
- var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0);
979
-
980
- if (row.Operation == RowOperation.None)
981
- {
982
- // Ignore the rows without section id.
983
- if (isSectionIdEmpty)
984
- {
985
- currentTable.Rows.RemoveAt(i);
986
- i--;
987
- }
988
- else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
989
- {
990
- keptRows++;
991
- }
992
- else
993
- {
994
- currentTable.Rows.RemoveAt(i);
995
- i--;
996
- }
997
- }
998
- else if (row.Operation == RowOperation.Modify)
999
- {
1000
- var sequenceChanged = row.Fields[2].Modified;
1001
- var conditionChanged = row.Fields[1].Modified;
1002
-
1003
- if (sequenceChanged && !conditionChanged)
1004
- {
1005
- keptRows++;
1006
- }
1007
- else if (!sequenceChanged && conditionChanged)
1008
- {
1009
- if (isSectionIdEmpty)
1010
- {
1011
- currentTable.Rows.RemoveAt(i);
1012
- i--;
1013
- }
1014
- else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1015
- {
1016
- keptRows++;
1017
- }
1018
- else
1019
- {
1020
- currentTable.Rows.RemoveAt(i);
1021
- i--;
1022
- }
1023
- }
1024
- else if (sequenceChanged && conditionChanged)
1025
- {
1026
- if (isSectionIdEmpty)
1027
- {
1028
- row.Fields[1].Modified = false;
1029
- keptRows++;
1030
- }
1031
- else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1032
- {
1033
- keptRows++;
1034
- }
1035
- else
1036
- {
1037
- row.Fields[1].Modified = false;
1038
- keptRows++;
1039
- }
1040
- }
1041
- }
1042
- else if (row.Operation == RowOperation.Delete)
1043
- {
1044
- if (isSectionIdEmpty)
1045
- {
1046
- // it is a stardard action which is added by wix, we should keep this action.
1047
- row.Operation = RowOperation.None;
1048
- keptRows++;
1049
- }
1050
- else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1051
- {
1052
- keptRows++;
1053
- }
1054
- else
1055
- {
1056
- if (customAction.ContainsKey(actionName))
1057
- {
1058
- currentTable.Rows.RemoveAt(i);
1059
- i--;
1060
- }
1061
- else
1062
- {
1063
- // it is a stardard action, we should keep this action.
1064
- row.Operation = RowOperation.None;
1065
- keptRows++;
1066
- }
1067
- }
1068
- }
1069
- else if (row.Operation == RowOperation.Add)
1070
- {
1071
- if (isSectionIdEmpty)
1072
- {
1073
- keptRows++;
1074
- }
1075
- else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1076
- {
1077
- keptRows++;
1078
- }
1079
- else
1080
- {
1081
- if (customAction.ContainsKey(actionName))
1082
- {
1083
- currentTable.Rows.RemoveAt(i);
1084
- i--;
1085
- }
1086
- else
1087
- {
1088
- keptRows++;
1089
- }
1090
- }
1091
- }
1092
- }
1093
- }
1094
-
1095
- return keptRows;
1096
- }
1097
-
577
/// <summary>
578
/// Create the #transform for the given main transform.
579
/// </summary>
src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
+15
-2
@@ -50,13 +50,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50
{
51
var patchTransforms = new List<PatchTransform>();
52
53
- var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType<WixPatchBaselineSymbol>();
53
+ var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols);
54
55
- foreach (var symbol in symbols)
55
+ var patchBaselineSymbols = symbols.OfType<WixPatchBaselineSymbol>();
56
+
57
+ var patchRefSymbols = symbols.OfType<WixPatchRefSymbol>().ToList();
58
+
59
+ foreach (var symbol in patchBaselineSymbols)
60
{
61
var targetData = this.GetWindowsInstallerData(symbol.BaselineFile.Path, BindStage.Target);
62
var updatedData = this.GetWindowsInstallerData(symbol.UpdateFile.Path, BindStage.Updated);
63
64
+ if (patchRefSymbols.Count > 0)
65
+ {
66
+ var targetCommand = new GenerateSectionIdsCommand(targetData);
67
+ targetCommand.Execute();
68
+
69
+ var updatedCommand = new GenerateSectionIdsCommand(updatedData);
70
+ updatedCommand.Execute();
71
+ }
72
+
73
var command = new GenerateTransformCommand(this.Messaging, targetData, updatedData, preserveUnchangedRows: true, showPedanticMessages: false);
74
var transform = command.Execute();
75
src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs
new
+225
@@ -0,0 +1,225 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Core.WindowsInstaller.Bind
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.Globalization;
8
+ using 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
+}
src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs
new
+549
@@ -0,0 +1,549 @@
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
+ private const char SectionDelimiter = '/';
15
+
16
+ public ReduceTransformCommand(Intermediate intermediate, IEnumerable<PatchTransform> patchTransforms)
17
+ {
18
+ this.Intermediate = intermediate;
19
+ this.PatchTransforms = patchTransforms;
20
+ }
21
+
22
+ private Intermediate Intermediate { get; }
23
+
24
+ private IEnumerable<PatchTransform> PatchTransforms { get; }
25
+
26
+ public void Execute()
27
+ {
28
+ var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList();
29
+
30
+ var patchRefSymbols = symbols.OfType<WixPatchRefSymbol>().ToList();
31
+
32
+ if (patchRefSymbols.Count > 0)
33
+ {
34
+ foreach (var patchTransform in this.PatchTransforms)
35
+ {
36
+ if (!this.ReduceTransform(patchTransform.Transform, patchRefSymbols))
37
+ {
38
+ // transform has none of the content authored into this patch
39
+ continue;
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ /// <summary>
46
+ /// Reduce the transform according to the patch references.
47
+ /// </summary>
48
+ /// <param name="transform">transform generated by torch.</param>
49
+ /// <param name="patchRefSymbols">Table contains patch family filter.</param>
50
+ /// <returns>true if the transform is not empty</returns>
51
+ private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefSymbol> patchRefSymbols)
52
+ {
53
+ // identify sections to keep
54
+ var oldSections = new Dictionary<string, Row>();
55
+ var newSections = new Dictionary<string, Row>();
56
+ var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>();
57
+ var sequenceList = new List<Table>();
58
+ var componentFeatureAddsIndex = new Dictionary<string, List<string>>();
59
+ var customActionTable = new Dictionary<string, Row>();
60
+ var directoryTableAdds = new Dictionary<string, Row>();
61
+ var featureTableAdds = new Dictionary<string, Row>();
62
+ var keptComponents = new Dictionary<string, Row>();
63
+ var keptDirectories = new Dictionary<string, Row>();
64
+ var keptFeatures = new Dictionary<string, Row>();
65
+ var keptLockPermissions = new HashSet<string>();
66
+ var keptMsiLockPermissionExs = new HashSet<string>();
67
+
68
+ var componentCreateFolderIndex = new Dictionary<string, List<string>>();
69
+ var directoryLockPermissionsIndex = new Dictionary<string, List<Row>>();
70
+ var directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>();
71
+
72
+ foreach (var patchRefSymbol in patchRefSymbols)
73
+ {
74
+ var tableName = patchRefSymbol.Table;
75
+ var key = patchRefSymbol.PrimaryKeys;
76
+
77
+ // Short circuit filtering if all changes should be included.
78
+ if ("*" == tableName && "*" == key)
79
+ {
80
+ RemoveProductCodeFromTransform(transform);
81
+ return true;
82
+ }
83
+
84
+ if (!transform.Tables.TryGetTable(tableName, out var table))
85
+ {
86
+ // Table not found.
87
+ continue;
88
+ }
89
+
90
+ // Index the table.
91
+ if (!tableKeyRows.TryGetValue(tableName, out var keyRows))
92
+ {
93
+ keyRows = table.Rows.ToDictionary(r => r.GetPrimaryKey());
94
+ tableKeyRows.Add(tableName, keyRows);
95
+ }
96
+
97
+ if (!keyRows.TryGetValue(key, out var row))
98
+ {
99
+ // Row not found.
100
+ continue;
101
+ }
102
+
103
+ // Differ.sectionDelimiter
104
+ var sections = row.SectionId.Split(SectionDelimiter);
105
+ oldSections[sections[0]] = row;
106
+ newSections[sections[1]] = row;
107
+ }
108
+
109
+ // throw away sections not referenced
110
+ var keptRows = 0;
111
+ Table directoryTable = null;
112
+ Table featureTable = null;
113
+ Table lockPermissionsTable = null;
114
+ Table msiLockPermissionsTable = null;
115
+
116
+ foreach (var table in transform.Tables)
117
+ {
118
+ if ("_SummaryInformation" == table.Name)
119
+ {
120
+ continue;
121
+ }
122
+
123
+ if (table.Name == "AdminExecuteSequence"
124
+ || table.Name == "AdminUISequence"
125
+ || table.Name == "AdvtExecuteSequence"
126
+ || table.Name == "InstallUISequence"
127
+ || table.Name == "InstallExecuteSequence")
128
+ {
129
+ sequenceList.Add(table);
130
+ continue;
131
+ }
132
+
133
+ for (var i = 0; i < table.Rows.Count; i++)
134
+ {
135
+ var row = table.Rows[i];
136
+
137
+ if (table.Name == "CreateFolder")
138
+ {
139
+ var createFolderComponentId = row.FieldAsString(1);
140
+
141
+ if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList))
142
+ {
143
+ directoryList = new List<string>();
144
+ componentCreateFolderIndex.Add(createFolderComponentId, directoryList);
145
+ }
146
+
147
+ directoryList.Add(row.FieldAsString(0));
148
+ }
149
+
150
+ if (table.Name == "CustomAction")
151
+ {
152
+ customActionTable.Add(row.FieldAsString(0), row);
153
+ }
154
+
155
+ if (table.Name == "Directory")
156
+ {
157
+ directoryTable = table;
158
+ if (RowOperation.Add == row.Operation)
159
+ {
160
+ directoryTableAdds.Add(row.FieldAsString(0), row);
161
+ }
162
+ }
163
+
164
+ if (table.Name == "Feature")
165
+ {
166
+ featureTable = table;
167
+ if (RowOperation.Add == row.Operation)
168
+ {
169
+ featureTableAdds.Add(row.FieldAsString(0), row);
170
+ }
171
+ }
172
+
173
+ if (table.Name == "FeatureComponents")
174
+ {
175
+ if (RowOperation.Add == row.Operation)
176
+ {
177
+ var featureId = row.FieldAsString(0);
178
+ var componentId = row.FieldAsString(1);
179
+
180
+ if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList))
181
+ {
182
+ featureList = new List<string>();
183
+ componentFeatureAddsIndex.Add(componentId, featureList);
184
+ }
185
+
186
+ featureList.Add(featureId);
187
+ }
188
+ }
189
+
190
+ if (table.Name == "LockPermissions")
191
+ {
192
+ lockPermissionsTable = table;
193
+ if ("CreateFolder" == row.FieldAsString(1))
194
+ {
195
+ var directoryId = row.FieldAsString(0);
196
+
197
+ if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList))
198
+ {
199
+ rowList = new List<Row>();
200
+ directoryLockPermissionsIndex.Add(directoryId, rowList);
201
+ }
202
+
203
+ rowList.Add(row);
204
+ }
205
+ }
206
+
207
+ if (table.Name == "MsiLockPermissionsEx")
208
+ {
209
+ msiLockPermissionsTable = table;
210
+ if ("CreateFolder" == row.FieldAsString(1))
211
+ {
212
+ var directoryId = row.FieldAsString(0);
213
+
214
+ if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList))
215
+ {
216
+ rowList = new List<Row>();
217
+ directoryMsiLockPermissionsExIndex.Add(directoryId, rowList);
218
+ }
219
+
220
+ rowList.Add(row);
221
+ }
222
+ }
223
+
224
+ if (null == row.SectionId)
225
+ {
226
+ table.Rows.RemoveAt(i);
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
+ {
235
+ table.Rows.RemoveAt(i);
236
+ i--;
237
+ }
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
+
245
+ if ("Directory" == table.Name)
246
+ {
247
+ 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
+ }
257
+ else
258
+ {
259
+ table.Rows.RemoveAt(i);
260
+ i--;
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
267
+
268
+ if (null != directoryTable)
269
+ {
270
+ foreach (var componentRow in keptComponents.Values)
271
+ {
272
+ var componentId = componentRow.FieldAsString(0);
273
+
274
+ if (RowOperation.Add == componentRow.Operation)
275
+ {
276
+ // Make sure each added component has its required directory and feature heirarchy.
277
+ var directoryId = componentRow.FieldAsString(2);
278
+ while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow))
279
+ {
280
+ if (!keptDirectories.ContainsKey(directoryId))
281
+ {
282
+ directoryTable.Rows.Add(directoryRow);
283
+ keptDirectories.Add(directoryId, directoryRow);
284
+ keptRows++;
285
+ }
286
+
287
+ directoryId = directoryRow.FieldAsString(1);
288
+ }
289
+
290
+ if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds))
291
+ {
292
+ foreach (var featureId in componentFeatureIds)
293
+ {
294
+ var currentFeatureId = featureId;
295
+ while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow))
296
+ {
297
+ if (!keptFeatures.ContainsKey(currentFeatureId))
298
+ {
299
+ featureTable.Rows.Add(featureRow);
300
+ keptFeatures.Add(currentFeatureId, featureRow);
301
+ keptRows++;
302
+ }
303
+
304
+ currentFeatureId = featureRow.FieldAsString(1);
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept.
311
+ foreach (var keptComponentId in keptComponents.Keys)
312
+ {
313
+ if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList))
314
+ {
315
+ foreach (var directoryId in directoryList)
316
+ {
317
+ if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList))
318
+ {
319
+ foreach (var lockPermissionsRow in lockPermissionsRowList)
320
+ {
321
+ var key = lockPermissionsRow.GetPrimaryKey('/');
322
+ if (keptLockPermissions.Add(key))
323
+ {
324
+ lockPermissionsTable.Rows.Add(lockPermissionsRow);
325
+ keptRows++;
326
+ }
327
+ }
328
+ }
329
+
330
+ if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList))
331
+ {
332
+ foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList)
333
+ {
334
+ var key = msiLockPermissionsExRow.GetPrimaryKey('/');
335
+ if (keptMsiLockPermissionExs.Add(key))
336
+ {
337
+ msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow);
338
+ keptRows++;
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ }
345
+ }
346
+ }
347
+
348
+ keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
349
+
350
+ // Delete tables that are empty.
351
+ var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList();
352
+
353
+ foreach (var tableName in tablesToDelete)
354
+ {
355
+ transform.Tables.Remove(tableName);
356
+ }
357
+
358
+ return keptRows > 0;
359
+ }
360
+
361
+ /// <summary>
362
+ /// Check if the section is in a PatchFamily.
363
+ /// </summary>
364
+ /// <param name="oldSection">Section id in target wixout</param>
365
+ /// <param name="newSection">Section id in upgrade wixout</param>
366
+ /// <param name="oldSections">Dictionary contains section id should be kept in the baseline wixout.</param>
367
+ /// <param name="newSections">Dictionary contains section id should be kept in the upgrade wixout.</param>
368
+ /// <returns>true if section in patch family</returns>
369
+ private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections)
370
+ {
371
+ var result = false;
372
+
373
+ if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection)))
374
+ {
375
+ result = true;
376
+ }
377
+ else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection)))
378
+ {
379
+ result = true;
380
+ }
381
+
382
+ return result;
383
+ }
384
+
385
+ /// <summary>
386
+ /// Remove the ProductCode property from the transform.
387
+ /// </summary>
388
+ /// <param name="transform">The transform.</param>
389
+ /// <remarks>
390
+ /// Changing the ProductCode is not supported in a patch.
391
+ /// </remarks>
392
+ private static void RemoveProductCodeFromTransform(WindowsInstallerData transform)
393
+ {
394
+ if (transform.Tables.TryGetTable("Property", out var propertyTable))
395
+ {
396
+ for (var i = 0; i < propertyTable.Rows.Count; ++i)
397
+ {
398
+ var propertyRow = propertyTable.Rows[i];
399
+ var property = propertyRow.FieldAsString(0);
400
+
401
+ if ("ProductCode" == property)
402
+ {
403
+ propertyTable.Rows.RemoveAt(i);
404
+ break;
405
+ }
406
+ }
407
+ }
408
+ }
409
+
410
+ /// <summary>
411
+ /// Reduce the transform sequence tables.
412
+ /// </summary>
413
+ /// <param name="sequenceList">ArrayList of tables to be reduced</param>
414
+ /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param>
415
+ /// <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>
417
+ /// <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)
419
+ {
420
+ var keptRows = 0;
421
+
422
+ foreach (var currentTable in sequenceList)
423
+ {
424
+ for (var i = 0; i < currentTable.Rows.Count; i++)
425
+ {
426
+ var row = currentTable.Rows[i];
427
+ var actionName = row.Fields[0].Data.ToString();
428
+ var sections = row.SectionId.Split(SectionDelimiter);
429
+ var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0);
430
+
431
+ if (row.Operation == RowOperation.None)
432
+ {
433
+ // Ignore the rows without section id.
434
+ if (isSectionIdEmpty)
435
+ {
436
+ currentTable.Rows.RemoveAt(i);
437
+ i--;
438
+ }
439
+ else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
440
+ {
441
+ keptRows++;
442
+ }
443
+ else
444
+ {
445
+ currentTable.Rows.RemoveAt(i);
446
+ i--;
447
+ }
448
+ }
449
+ else if (row.Operation == RowOperation.Modify)
450
+ {
451
+ var sequenceChanged = row.Fields[2].Modified;
452
+ var conditionChanged = row.Fields[1].Modified;
453
+
454
+ if (sequenceChanged && !conditionChanged)
455
+ {
456
+ keptRows++;
457
+ }
458
+ else if (!sequenceChanged && conditionChanged)
459
+ {
460
+ if (isSectionIdEmpty)
461
+ {
462
+ currentTable.Rows.RemoveAt(i);
463
+ i--;
464
+ }
465
+ else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
466
+ {
467
+ keptRows++;
468
+ }
469
+ else
470
+ {
471
+ currentTable.Rows.RemoveAt(i);
472
+ i--;
473
+ }
474
+ }
475
+ else if (sequenceChanged && conditionChanged)
476
+ {
477
+ if (isSectionIdEmpty)
478
+ {
479
+ row.Fields[1].Modified = false;
480
+ keptRows++;
481
+ }
482
+ else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
483
+ {
484
+ keptRows++;
485
+ }
486
+ else
487
+ {
488
+ row.Fields[1].Modified = false;
489
+ keptRows++;
490
+ }
491
+ }
492
+ }
493
+ else if (row.Operation == RowOperation.Delete)
494
+ {
495
+ if (isSectionIdEmpty)
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
+ {
503
+ keptRows++;
504
+ }
505
+ else
506
+ {
507
+ if (customAction.ContainsKey(actionName))
508
+ {
509
+ currentTable.Rows.RemoveAt(i);
510
+ i--;
511
+ }
512
+ else
513
+ {
514
+ // it is a stardard action, we should keep this action.
515
+ row.Operation = RowOperation.None;
516
+ keptRows++;
517
+ }
518
+ }
519
+ }
520
+ else if (row.Operation == RowOperation.Add)
521
+ {
522
+ if (isSectionIdEmpty)
523
+ {
524
+ keptRows++;
525
+ }
526
+ else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
527
+ {
528
+ keptRows++;
529
+ }
530
+ else
531
+ {
532
+ if (customAction.ContainsKey(actionName))
533
+ {
534
+ currentTable.Rows.RemoveAt(i);
535
+ i--;
536
+ }
537
+ else
538
+ {
539
+ keptRows++;
540
+ }
541
+ }
542
+ }
543
+ }
544
+ }
545
+
546
+ return keptRows;
547
+ }
548
+ }
549
+}
src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
+7
-1
@@ -39,10 +39,16 @@ namespace WixToolset.Core.WindowsInstaller
39
patchTransforms = command.Execute();
40
}
41
42
+ // Reduce transforms.
43
+ {
44
+ var command = new ReduceTransformCommand(context.IntermediateRepresentation, patchTransforms);
45
+ command.Execute();
46
+ }
47
+
48
// Enhance the intermediate by attaching the created patch transforms.
49
IEnumerable<SubStorage> subStorages;
50
{
45
- var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms);
51
+ var command = new CreatePatchSubStoragesCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms);
52
subStorages = command.Execute();
53
}
54
src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+9
-211
@@ -21,8 +21,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
21
{
22
private static readonly Regex Modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}");
23
24
- private int sectionCount;
25
-
24
public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, string databasePath, OutputType outputType, string exportBasePath, string extractFilesFolder, string intermediateFolder, bool enableDemodularization, bool skipSummaryInfo)
25
{
26
this.Messaging = messaging;
@@ -65,6 +63,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
63
64
public bool AdminImage { get; private set; }
65
66
+ public WindowsInstallerData Data { get; private set; }
67
+
68
public IEnumerable<string> ExportedFiles { get; private set; }
69
70
public WindowsInstallerData Execute()
@@ -72,7 +72,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
72
var adminImage = false;
73
var exportedFiles = new List<string>();
74
75
- var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath))
75
+ var data = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath))
76
{
77
Type = this.OutputType
78
};
@@ -85,15 +85,13 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
85
86
Directory.CreateDirectory(this.IntermediateFolder);
87
88
- output.Codepage = this.GetCodePage();
88
+ data.Codepage = this.GetCodePage();
89
90
- var modularizationGuid = this.ProcessTables(output, exportedFiles);
90
+ var modularizationGuid = this.ProcessTables(data, exportedFiles);
91
92
- var summaryInfo = this.ProcessSummaryInfo(output, modularizationGuid);
92
+ var summaryInfo = this.ProcessSummaryInfo(data, modularizationGuid);
93
94
- this.UpdateUnrealFileColumns(this.DatabasePath, output, summaryInfo, exportedFiles);
95
-
96
- this.GenerateSectionIds(output);
94
+ this.UpdateUnrealFileColumns(this.DatabasePath, data, summaryInfo, exportedFiles);
95
}
96
}
97
catch (Win32Exception e)
@@ -107,9 +105,10 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
105
}
106
107
this.AdminImage = adminImage;
108
+ this.Data = data;
109
this.ExportedFiles = exportedFiles;
110
112
- return output;
111
+ return data;
112
}
113
114
private int GetCodePage()
@@ -657,207 +656,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
656
}
657
}
658
660
- /// <summary>
661
- /// Creates section ids on rows which form logical groupings of resources.
662
- /// </summary>
663
- /// <param name="output">The Output that represents the msi database.</param>
664
- private void GenerateSectionIds(WindowsInstallerData output)
665
- {
666
- // First assign and index section ids for the tables that are in their own sections.
667
- this.AssignSectionIdsToTable(output.Tables["Binary"], 0);
668
- var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0);
669
- var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0);
670
- this.AssignSectionIdsToTable(output.Tables["Directory"], 0);
671
- var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0);
672
- this.AssignSectionIdsToTable(output.Tables["Icon"], 0);
673
- var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0);
674
- this.AssignSectionIdsToTable(output.Tables["Property"], 0);
675
-
676
- // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here.
677
- var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0);
678
- var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5);
679
- var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0);
680
- var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0);
681
- var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0);
682
- var serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0);
683
-
684
- // Now handle all the tables which only rely on previous indexes and order does not matter.
685
- foreach (var table in output.Tables)
686
- {
687
- switch (table.Name)
688
- {
689
- case "MsiFileHash":
690
- ConnectTableToSection(table, fileSectionIdIndex, 0);
691
- break;
692
- case "MsiAssembly":
693
- case "MsiAssemblyName":
694
- ConnectTableToSection(table, componentSectionIdIndex, 0);
695
- break;
696
- case "MsiPackageCertificate":
697
- case "MsiPatchCertificate":
698
- ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1);
699
- break;
700
- case "CreateFolder":
701
- case "FeatureComponents":
702
- case "MoveFile":
703
- case "ReserveCost":
704
- case "ODBCTranslator":
705
- ConnectTableToSection(table, componentSectionIdIndex, 1);
706
- break;
707
- case "TypeLib":
708
- ConnectTableToSection(table, componentSectionIdIndex, 2);
709
- break;
710
- case "Shortcut":
711
- case "Environment":
712
- ConnectTableToSection(table, componentSectionIdIndex, 3);
713
- break;
714
- case "RemoveRegistry":
715
- ConnectTableToSection(table, componentSectionIdIndex, 4);
716
- break;
717
- case "ServiceControl":
718
- ConnectTableToSection(table, componentSectionIdIndex, 5);
719
- break;
720
- case "IniFile":
721
- case "RemoveIniFile":
722
- ConnectTableToSection(table, componentSectionIdIndex, 7);
723
- break;
724
- case "AppId":
725
- ConnectTableToSection(table, appIdSectionIdIndex, 0);
726
- break;
727
- case "Condition":
728
- ConnectTableToSection(table, featureSectionIdIndex, 0);
729
- break;
730
- case "ODBCSourceAttribute":
731
- ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0);
732
- break;
733
- case "ODBCAttribute":
734
- ConnectTableToSection(table, odbcDriverSectionIdIndex, 0);
735
- break;
736
- case "AdminExecuteSequence":
737
- case "AdminUISequence":
738
- case "AdvtExecuteSequence":
739
- case "AdvtUISequence":
740
- case "InstallExecuteSequence":
741
- case "InstallUISequence":
742
- ConnectTableToSection(table, customActionSectionIdIndex, 0);
743
- break;
744
- case "LockPermissions":
745
- case "MsiLockPermissions":
746
- foreach (var row in table.Rows)
747
- {
748
- var lockObject = (string)row[0];
749
- var tableName = (string)row[1];
750
- switch (tableName)
751
- {
752
- case "File":
753
- row.SectionId = (string)fileSectionIdIndex[lockObject];
754
- break;
755
- case "Registry":
756
- row.SectionId = (string)registrySectionIdIndex[lockObject];
757
- break;
758
- case "ServiceInstall":
759
- row.SectionId = (string)serviceInstallSectionIdIndex[lockObject];
760
- break;
761
- }
762
- }
763
- break;
764
- }
765
- }
766
-
767
- // Now pass the output to each unbinder extension to allow them to analyze the output and determine their proper section ids.
768
- //foreach (IUnbinderExtension extension in this.unbinderExtensions)
769
- //{
770
- // extension.GenerateSectionIds(output);
771
- //}
772
- }
773
-
774
- /// <summary>
775
- /// Creates new section ids on all the rows in a table.
776
- /// </summary>
777
- /// <param name="table">The table to add sections to.</param>
778
- /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
779
- /// <returns>A dictionary containing the tables key for each row paired with its assigned section id.</returns>
780
- private Dictionary<string, string> AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex)
781
- {
782
- var primaryKeyToSectionId = new Dictionary<string, string>();
783
-
784
- if (null != table)
785
- {
786
- foreach (var row in table.Rows)
787
- {
788
- row.SectionId = this.GetNewSectionId();
789
-
790
- primaryKeyToSectionId.Add(row.FieldAsString(rowPrimaryKeyIndex), row.SectionId);
791
- }
792
- }
793
-
794
- return primaryKeyToSectionId;
795
- }
796
-
797
- /// <summary>
798
- /// Connects a table's rows to an already sectioned table.
799
- /// </summary>
800
- /// <param name="table">The table containing rows that need to be connected to sections.</param>
801
- /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param>
802
- /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
803
- private static void ConnectTableToSection(Table table, Dictionary<string, string> sectionIdIndex, int rowIndex)
804
- {
805
- if (null != table)
806
- {
807
- foreach (var row in table.Rows)
808
- {
809
- if (sectionIdIndex.TryGetValue(row.FieldAsString(rowIndex), out var sectionId))
810
- {
811
- row.SectionId = sectionId;
812
- }
813
- }
814
- }
815
- }
816
-
817
- /// <summary>
818
- /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it.
819
- /// </summary>
820
- /// <param name="table">The table containing rows that need to be connected to sections.</param>
821
- /// <param name="sectionIdIndex">A dictionary containing keys to map table to its section.</param>
822
- /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
823
- /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
824
- /// <returns>A dictionary containing the tables key for each row paired with its assigned section id.</returns>
825
- private static Dictionary<string, string> ConnectTableToSectionAndIndex(Table table, Dictionary<string, string> sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex)
826
- {
827
- var newPrimaryKeyToSectionId = new Dictionary<string, string>();
828
-
829
- if (null != table)
830
- {
831
- foreach (var row in table.Rows)
832
- {
833
- var foreignKey = row.FieldAsString(rowIndex);
834
-
835
- if (!sectionIdIndex.TryGetValue(foreignKey, out var sectionId))
836
- {
837
- continue;
838
- }
839
-
840
- row.SectionId = sectionId;
841
-
842
- var primaryKey = row.FieldAsString(rowPrimaryKeyIndex);
843
-
844
- if (!String.IsNullOrEmpty(primaryKey) && sectionIdIndex.ContainsKey(primaryKey))
845
- {
846
- newPrimaryKeyToSectionId.Add(primaryKey, row.SectionId);
847
- }
848
- }
849
- }
850
-
851
- return newPrimaryKeyToSectionId;
852
- }
853
-
854
- private string GetNewSectionId()
855
- {
856
- this.sectionCount++;
857
-
858
- return "wix.section." + this.sectionCount.ToString(CultureInfo.InvariantCulture);
859
- }
860
-
659
private class SummaryInformationBits
660
{
661
public bool AdminImage { get; set; }
src/wix/WixToolset.Core/Compiler.cs
+3
-3
@@ -5793,13 +5793,13 @@ namespace WixToolset.Core
5793
this.ParseCertificatesElement(child);
5794
break;
5795
case "PatchFamily":
5796
- this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id);
5796
+ this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id?.Id);
5797
break;
5798
case "PatchFamilyGroup":
5799
- this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id);
5799
+ this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id);
5800
break;
5801
case "PatchFamilyGroupRef":
5802
- this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id);
5802
+ this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id?.Id);
5803
break;
5804
case "PayloadGroup":
5805
this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null);
src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
+29
@@ -394,6 +394,35 @@ namespace WixToolsetTest.CoreIntegration
394
}
395
}
396
397
+ [Fact]
398
+ public void CanBuildPatchWithFiltering()
399
+ {
400
+ var sourceFolder = TestData.Get(@"TestData", "PatchFamilyFilter");
401
+
402
+ using (var fs = new DisposableFileSystem())
403
+ {
404
+ var baseFolder = fs.GetFolder();
405
+ var tempFolderPatch = Path.Combine(baseFolder, "patch");
406
+
407
+ var patchPath = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(this.templateBaselinePdb), Path.GetDirectoryName(this.templateUpdatePdb) });
408
+
409
+ var doc = GetExtractPatchXml(patchPath);
410
+ WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value);
411
+
412
+ var names = Query.GetSubStorageNames(patchPath);
413
+ WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
414
+
415
+ var cab = Path.Combine(baseFolder, "foo.cab");
416
+ Query.ExtractStream(patchPath, "foo.cab", cab);
417
+
418
+ var files = Query.GetCabinetFiles(cab);
419
+ var file = files.Single();
420
+ WixAssert.StringEqual("a.txt", file.Name);
421
+ var contents = file.OpenText().ReadToEnd();
422
+ WixAssert.StringEqual("This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.\r\n", contents);
423
+ }
424
+ }
425
+
426
private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB, IEnumerable<string> bindpaths = null)
427
{
428
var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs
+2
-2
@@ -1,8 +1,8 @@
1
-<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
<Patch AllowRemoval="yes" Manufacturer="FireGiant" MoreInfoURL="http://www.example.com/" DisplayName="~Test Patch v$(var.V)" Description="~Test Small Update Patch v($var.V)" Classification="Update">
3
4
<Media Id="1" Cabinet="foo.cab">
5
- <PatchBaseline Id="RTM" />
5
+ <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
6
</Media>
7
8
<PatchFamilyRef Id="SamplePatchFamily" />