@joebigelow / wix / commits / 28a1a91c

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" />