aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-09-27 01:44:33 -0700
committerRob Mensching <rob@firegiant.com>2022-09-27 16:25:20 -0700
commit28a1a91cd2c20486276ee37dacce623ed8a75d6e (patch)
tree393c371ba8e3df5e06151005685ccd852689db6f
parent7113e775cf68aba493c5e769e8e5584db15d1974 (diff)
downloadwix-28a1a91cd2c20486276ee37dacce623ed8a75d6e.tar.gz
wix-28a1a91cd2c20486276ee37dacce623ed8a75d6e.tar.bz2
wix-28a1a91cd2c20486276ee37dacce623ed8a75d6e.zip
Refactor patch filtering on the path towards making it work properly
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchSubStoragesCommand.cs (renamed from src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs)529
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs17
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs225
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs549
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs8
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs220
-rw-r--r--src/wix/WixToolset.Core/Compiler.cs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs29
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs4
9 files changed, 843 insertions, 744 deletions
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchSubStoragesCommand.cs
index 6d37fdc2..db121137 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchSubStoragesCommand.cs
@@ -17,7 +17,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
17 /// <summary> 17 /// <summary>
18 /// Include transforms in a patch. 18 /// Include transforms in a patch.
19 /// </summary> 19 /// </summary>
20 internal class AttachPatchTransformsCommand 20 internal class CreatePatchSubStoragesCommand
21 { 21 {
22 private static readonly string[] PatchUninstallBreakingTables = new[] 22 private static readonly string[] PatchUninstallBreakingTables = new[]
23 { 23 {
@@ -55,7 +55,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
55 55
56 private readonly TableDefinitionCollection tableDefinitions; 56 private readonly TableDefinitionCollection tableDefinitions;
57 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 { 59 {
60 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); 60 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
61 this.Messaging = messaging; 61 this.Messaging = messaging;
@@ -126,22 +126,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
126 126
127 foreach (var mainTransform in this.Transforms) 127 foreach (var mainTransform in this.Transforms)
128 { 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
141 // Validate the transform doesn't break any patch specific rules. 129 // Validate the transform doesn't break any patch specific rules.
142 this.Validate(mainTransform); 130 this.Validate(mainTransform);
143 131
144 // ensure consistent File.Sequence within each Media 132 // ensure consistent File.Sequence within each Media
133 var baselineSymbol = baselineSymbolsById[mainTransform.Baseline];
145 var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; 134 var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId];
146 135
147 // Ensure that files are sequenced after the last file in any transform. 136 // Ensure that files are sequenced after the last file in any transform.
@@ -383,331 +372,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
383 return success; 372 return success;
384 } 373 }
385 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
708 private void Validate(PatchTransform patchTransform) 375 private void Validate(PatchTransform patchTransform)
709 { 376 {
710 var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it? 377 var transformPath = patchTransform.Baseline;
711 var transform = patchTransform.Transform; 378 var transform = patchTransform.Transform;
712 379
713 // Changing the ProdocutCode in a patch transform is not recommended. 380 // Changing the ProdocutCode in a patch transform is not recommended.
@@ -908,194 +575,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
908 } 575 }
909 576
910 /// <summary> 577 /// <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
1098 /// <summary>
1099 /// Create the #transform for the given main transform. 578 /// Create the #transform for the given main transform.
1100 /// </summary> 579 /// </summary>
1101 private WindowsInstallerData BuildPairedTransform(Dictionary<SummaryInformationType, SummaryInformationSymbol> summaryInfo, Dictionary<string, MsiPatchMetadataSymbol> patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) 580 private WindowsInstallerData BuildPairedTransform(Dictionary<SummaryInformationType, SummaryInformationSymbol> summaryInfo, Dictionary<string, MsiPatchMetadataSymbol> patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode)
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
index 6d5bff69..17583e96 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
@@ -50,13 +50,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50 { 50 {
51 var patchTransforms = new List<PatchTransform>(); 51 var patchTransforms = new List<PatchTransform>();
52 52
53 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType<WixPatchBaselineSymbol>(); 53 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols);
54 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)
56 { 60 {
57 var targetData = this.GetWindowsInstallerData(symbol.BaselineFile.Path, BindStage.Target); 61 var targetData = this.GetWindowsInstallerData(symbol.BaselineFile.Path, BindStage.Target);
58 var updatedData = this.GetWindowsInstallerData(symbol.UpdateFile.Path, BindStage.Updated); 62 var updatedData = this.GetWindowsInstallerData(symbol.UpdateFile.Path, BindStage.Updated);
59 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
60 var command = new GenerateTransformCommand(this.Messaging, targetData, updatedData, preserveUnchangedRows: true, showPedanticMessages: false); 73 var command = new GenerateTransformCommand(this.Messaging, targetData, updatedData, preserveUnchangedRows: true, showPedanticMessages: false);
61 var transform = command.Execute(); 74 var transform = command.Execute();
62 75
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs
new file mode 100644
index 00000000..c7bebbed
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateSectionIdsCommand.cs
@@ -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
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/ReduceTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs
new file mode 100644
index 00000000..4966a0b4
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ReduceTransformCommand.cs
@@ -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
3namespace 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}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
index bccdd3d4..d1d7c19b 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
@@ -39,10 +39,16 @@ namespace WixToolset.Core.WindowsInstaller
39 patchTransforms = command.Execute(); 39 patchTransforms = command.Execute();
40 } 40 }
41 41
42 // Reduce transforms.
43 {
44 var command = new ReduceTransformCommand(context.IntermediateRepresentation, patchTransforms);
45 command.Execute();
46 }
47
42 // Enhance the intermediate by attaching the created patch transforms. 48 // Enhance the intermediate by attaching the created patch transforms.
43 IEnumerable<SubStorage> subStorages; 49 IEnumerable<SubStorage> subStorages;
44 { 50 {
45 var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms); 51 var command = new CreatePatchSubStoragesCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms);
46 subStorages = command.Execute(); 52 subStorages = command.Execute();
47 } 53 }
48 54
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
index 7bbbbd76..5eeb67c8 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
@@ -21,8 +21,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
21 { 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}"); 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 23
24 private int sectionCount;
25
26 public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, string databasePath, OutputType outputType, string exportBasePath, string extractFilesFolder, string intermediateFolder, bool enableDemodularization, bool skipSummaryInfo) 24 public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, string databasePath, OutputType outputType, string exportBasePath, string extractFilesFolder, string intermediateFolder, bool enableDemodularization, bool skipSummaryInfo)
27 { 25 {
28 this.Messaging = messaging; 26 this.Messaging = messaging;
@@ -65,6 +63,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
65 63
66 public bool AdminImage { get; private set; } 64 public bool AdminImage { get; private set; }
67 65
66 public WindowsInstallerData Data { get; private set; }
67
68 public IEnumerable<string> ExportedFiles { get; private set; } 68 public IEnumerable<string> ExportedFiles { get; private set; }
69 69
70 public WindowsInstallerData Execute() 70 public WindowsInstallerData Execute()
@@ -72,7 +72,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
72 var adminImage = false; 72 var adminImage = false;
73 var exportedFiles = new List<string>(); 73 var exportedFiles = new List<string>();
74 74
75 var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)) 75 var data = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath))
76 { 76 {
77 Type = this.OutputType 77 Type = this.OutputType
78 }; 78 };
@@ -85,15 +85,13 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
85 85
86 Directory.CreateDirectory(this.IntermediateFolder); 86 Directory.CreateDirectory(this.IntermediateFolder);
87 87
88 output.Codepage = this.GetCodePage(); 88 data.Codepage = this.GetCodePage();
89 89
90 var modularizationGuid = this.ProcessTables(output, exportedFiles); 90 var modularizationGuid = this.ProcessTables(data, exportedFiles);
91 91
92 var summaryInfo = this.ProcessSummaryInfo(output, modularizationGuid); 92 var summaryInfo = this.ProcessSummaryInfo(data, modularizationGuid);
93 93
94 this.UpdateUnrealFileColumns(this.DatabasePath, output, summaryInfo, exportedFiles); 94 this.UpdateUnrealFileColumns(this.DatabasePath, data, summaryInfo, exportedFiles);
95
96 this.GenerateSectionIds(output);
97 } 95 }
98 } 96 }
99 catch (Win32Exception e) 97 catch (Win32Exception e)
@@ -107,9 +105,10 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
107 } 105 }
108 106
109 this.AdminImage = adminImage; 107 this.AdminImage = adminImage;
108 this.Data = data;
110 this.ExportedFiles = exportedFiles; 109 this.ExportedFiles = exportedFiles;
111 110
112 return output; 111 return data;
113 } 112 }
114 113
115 private int GetCodePage() 114 private int GetCodePage()
@@ -657,207 +656,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
657 } 656 }
658 } 657 }
659 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
861 private class SummaryInformationBits 659 private class SummaryInformationBits
862 { 660 {
863 public bool AdminImage { get; set; } 661 public bool AdminImage { get; set; }
diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs
index 8f1ae7fb..e100c5be 100644
--- a/src/wix/WixToolset.Core/Compiler.cs
+++ b/src/wix/WixToolset.Core/Compiler.cs
@@ -5793,13 +5793,13 @@ namespace WixToolset.Core
5793 this.ParseCertificatesElement(child); 5793 this.ParseCertificatesElement(child);
5794 break; 5794 break;
5795 case "PatchFamily": 5795 case "PatchFamily":
5796 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id); 5796 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id?.Id);
5797 break; 5797 break;
5798 case "PatchFamilyGroup": 5798 case "PatchFamilyGroup":
5799 this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id); 5799 this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id);
5800 break; 5800 break;
5801 case "PatchFamilyGroupRef": 5801 case "PatchFamilyGroupRef":
5802 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id); 5802 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id?.Id);
5803 break; 5803 break;
5804 case "PayloadGroup": 5804 case "PayloadGroup":
5805 this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null); 5805 this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null);
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
index 945c346b..b0d49c05 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
@@ -394,6 +394,35 @@ namespace WixToolsetTest.CoreIntegration
394 } 394 }
395 } 395 }
396 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
397 private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB, IEnumerable<string> bindpaths = null) 426 private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB, IEnumerable<string> bindpaths = null)
398 { 427 {
399 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); 428 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs
index d39170c0..f48fd1ef 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs
@@ -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"> 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 3
4 <Media Id="1" Cabinet="foo.cab"> 4 <Media Id="1" Cabinet="foo.cab">
5 <PatchBaseline Id="RTM" /> 5 <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
6 </Media> 6 </Media>
7 7
8 <PatchFamilyRef Id="SamplePatchFamily" /> 8 <PatchFamilyRef Id="SamplePatchFamily" />