@joebigelow / wix-1 / commits / cff729a3

Improve instance transforms

Fix bug where the string `UpgradeCode` was being used instead of the GUID for the UpgradeCode in an instance transform. Also handle the case where the instance transform's Property does or does not exist when creating the transform. Fixes 7193

Rob Mensching committed Feb 9, 2023 at 13:59 UTC cff729a33833a963bbaeca9def11b7c09f8c2c0b
6 files changed +202 -143
src/internal/WixInternal.TestSupport/Query.cs
+81 -46
@@ -26,6 +26,20 @@ namespace WixInternal.TestSupport
26 return results.ToArray();
27 }
28
29 + public static string[] QueryDatabase(Database db, string[] tables)
30 + {
31 + var results = new List<string>();
32 + var resultsByTable = QueryDatabaseByTable(db, tables);
33 + var sortedTables = tables.ToList();
34 + sortedTables.Sort();
35 + foreach (var tableName in sortedTables)
36 + {
37 + var rows = resultsByTable[tableName];
38 + rows?.ForEach(r => results.Add($"{tableName}:{r}"));
39 + }
40 + return results.ToArray();
41 + }
42 +
43 /// <summary>
44 /// Returns rows from requested tables formatted to facilitate testing.
45 /// If the table did not exist in the database, its list will be null.
@@ -39,68 +53,89 @@ namespace WixInternal.TestSupport
53
54 if (tables?.Length > 0)
55 {
42 - var sb = new StringBuilder();
56 using (var db = new Database(path))
57 {
45 - foreach (var table in tables)
58 + results = QueryDatabaseByTable(db, tables);
59 + }
60 + }
61 +
62 + return results;
63 + }
64 +
65 + /// <summary>
66 + /// Returns rows from requested tables formatted to facilitate testing.
67 + /// If the table did not exist in the database, its list will be null.
68 + /// </summary>
69 + /// <param name="db"></param>
70 + /// <param name="tables"></param>
71 + /// <returns></returns>
72 + public static Dictionary<string, List<string>> QueryDatabaseByTable(Database db, string[] tables)
73 + {
74 + var results = new Dictionary<string, List<string>>();
75 +
76 + if (tables?.Length > 0)
77 + {
78 + var sb = new StringBuilder();
79 +
80 + foreach (var table in tables)
81 + {
82 + if (table == "_SummaryInformation")
83 {
47 - if (table == "_SummaryInformation")
48 - {
49 - var entries = new List<string>();
50 - results.Add(table, entries);
51 -
52 - entries.Add($"Title\t{db.SummaryInfo.Title}");
53 - entries.Add($"Subject\t{db.SummaryInfo.Subject}");
54 - entries.Add($"Author\t{db.SummaryInfo.Author}");
55 - entries.Add($"Keywords\t{db.SummaryInfo.Keywords}");
56 - entries.Add($"Comments\t{db.SummaryInfo.Comments}");
57 - entries.Add($"Template\t{db.SummaryInfo.Template}");
58 - entries.Add($"CodePage\t{db.SummaryInfo.CodePage}");
59 - entries.Add($"PageCount\t{db.SummaryInfo.PageCount}");
60 - entries.Add($"WordCount\t{db.SummaryInfo.WordCount}");
61 - entries.Add($"CharacterCount\t{db.SummaryInfo.CharacterCount}");
62 - entries.Add($"Security\t{db.SummaryInfo.Security}");
63 -
64 - continue;
65 - }
84 + var entries = new List<string>();
85 + results.Add(table, entries);
86 +
87 + entries.Add($"Title\t{db.SummaryInfo.Title}");
88 + entries.Add($"Subject\t{db.SummaryInfo.Subject}");
89 + entries.Add($"Author\t{db.SummaryInfo.Author}");
90 + entries.Add($"Keywords\t{db.SummaryInfo.Keywords}");
91 + entries.Add($"Comments\t{db.SummaryInfo.Comments}");
92 + entries.Add($"Template\t{db.SummaryInfo.Template}");
93 + entries.Add($"CodePage\t{db.SummaryInfo.CodePage}");
94 + entries.Add($"PageCount\t{db.SummaryInfo.PageCount}");
95 + entries.Add($"WordCount\t{db.SummaryInfo.WordCount}");
96 + entries.Add($"CharacterCount\t{db.SummaryInfo.CharacterCount}");
97 + entries.Add($"Security\t{db.SummaryInfo.Security}");
98 +
99 + continue;
100 + }
101
67 - if (!db.IsTablePersistent(table))
68 - {
69 - results.Add(table, null);
70 - continue;
71 - }
102 + if (!db.IsTablePersistent(table))
103 + {
104 + results.Add(table, null);
105 + continue;
106 + }
107
73 - var rows = new List<string>();
74 - results.Add(table, rows);
108 + var rows = new List<string>();
109 + results.Add(table, rows);
110
76 - using (var view = db.OpenView("SELECT * FROM `{0}`", table))
111 + using (var view = db.OpenView("SELECT * FROM `{0}`", table))
112 + {
113 + view.Execute();
114 +
115 + Record record;
116 + while ((record = view.Fetch()) != null)
117 {
78 - view.Execute();
118 + sb.Clear();
119
80 - Record record;
81 - while ((record = view.Fetch()) != null)
120 + using (record)
121 {
83 - sb.Clear();
84 -
85 - using (record)
122 + for (var i = 0; i < record.FieldCount; ++i)
123 {
87 - for (var i = 0; i < record.FieldCount; ++i)
124 + if (i > 0)
125 {
89 - if (i > 0)
90 - {
91 - sb.Append("\t");
92 - }
93 -
94 - sb.Append(record[i + 1]?.ToString());
126 + sb.Append("\t");
127 }
96 - }
128
98 - rows.Add(sb.ToString());
129 + sb.Append(record[i + 1]?.ToString());
130 + }
131 }
132 +
133 + rows.Add(sb.ToString());
134 }
101 - rows.Sort();
135 }
103 - }
136 +
137 + rows.Sort();
138 + }
139 }
140
141 return results;
src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+1 -1
@@ -76,7 +76,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
76 }
77 else if ("UpgradeCode" == id)
78 {
79 - updatedUpgradeCode = id;
79 + updatedUpgradeCode = row.FieldAsString(1);
80 propertyTable.Rows.RemoveAt(i);
81 }
82 }
src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs
+14 -29
@@ -41,43 +41,27 @@ namespace WixToolset.Core.WindowsInstaller.Bind
41
42 if (wixInstanceTransformsSymbols.Any())
43 {
44 - string targetProductCode = null;
45 - string targetUpgradeCode = null;
46 - string targetProductVersion = null;
47 -
44 var targetSummaryInformationTable = this.Output.Tables["_SummaryInformation"];
49 - var targetPropertyTable = this.Output.Tables["Property"];
45 + var targetPropertyRows = new RowDictionary<PropertyRow>(this.Output.Tables["Property"]);
46 + PropertyRow propertyRow = null;
47
48 // Get the data from target database
52 - foreach (var propertyRow in targetPropertyTable.Rows)
53 - {
54 - if ("ProductCode" == (string)propertyRow[0])
55 - {
56 - targetProductCode = (string)propertyRow[1];
57 - }
58 - else if ("ProductVersion" == (string)propertyRow[0])
59 - {
60 - targetProductVersion = (string)propertyRow[1];
61 - }
62 - else if ("UpgradeCode" == (string)propertyRow[0])
63 - {
64 - targetUpgradeCode = (string)propertyRow[1];
65 - }
66 - }
49 + var targetProductCode = targetPropertyRows.TryGetValue("ProductCode", out propertyRow) ? propertyRow.Value : null;
50 + var targetProductVersion = targetPropertyRows.TryGetValue("ProductVersion", out propertyRow) ? propertyRow.Value : null;
51 + var targetUpgradeCode = targetPropertyRows.TryGetValue("UpgradeCode", out propertyRow) ? propertyRow.Value : null;
52
53 // Index the Instance Component Rows, we'll get the Components rows from the real Component table.
69 - var targetInstanceComponentTable = this.Section.Symbols.OfType<WixInstanceComponentSymbol>();
70 - var instanceComponentGuids = targetInstanceComponentTable.ToDictionary(t => t.Id.Id, t => (ComponentRow)null);
54 + var instanceComponentSymbols = this.Section.Symbols.OfType<WixInstanceComponentSymbol>().ToDictionary(t => t.Id.Id, t => (ComponentRow)null);
55
72 - if (instanceComponentGuids.Any())
56 + if (instanceComponentSymbols.Any())
57 {
58 var targetComponentTable = this.Output.Tables["Component"];
59 foreach (ComponentRow componentRow in targetComponentTable.Rows)
60 {
61 var component = (string)componentRow[0];
78 - if (instanceComponentGuids.ContainsKey(component))
62 + if (instanceComponentSymbols.ContainsKey(component))
63 {
80 - instanceComponentGuids[component] = componentRow;
64 + instanceComponentSymbols[component] = componentRow;
65 }
66 }
67 }
@@ -123,9 +107,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
107 productCodeRow[0] = "ProductCode";
108 productCodeRow[1] = productCode;
109
126 - // Change the instance property
110 + // Set the instance property. If the property exists in the MSI already, mark the row as modified in the transform.
111 + // Otherwise, we need to mark the row as an add.
112 var instanceIdRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers);
128 - instanceIdRow.Operation = RowOperation.Modify;
113 + instanceIdRow.Operation = targetPropertyRows.ContainsKey(instanceSymbol.PropertyId) ? RowOperation.Modify : RowOperation.Add;
114 instanceIdRow.Fields[1].Modified = true;
115 instanceIdRow[0] = instanceSymbol.PropertyId;
116 instanceIdRow[1] = instanceId;
@@ -192,10 +177,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
177 }
178
179 // If there are instance Components generate new GUIDs for them.
195 - if (0 < instanceComponentGuids.Count)
180 + if (0 < instanceComponentSymbols.Count)
181 {
182 var componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]);
198 - foreach (var targetComponentRow in instanceComponentGuids.Values)
183 + foreach (var targetComponentRow in instanceComponentSymbols.Values)
184 {
185 var guid = targetComponentRow.Guid;
186 if (!String.IsNullOrEmpty(guid))
src/wix/test/WixToolsetTest.CoreIntegration/InstanceTransformFixture.cs new
+103
@@ -0,0 +1,103 @@
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 WixToolsetTest.CoreIntegration
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Linq;
8 + using WixInternal.Core.TestPackage;
9 + using WixInternal.TestSupport;
10 + using WixToolset.Data.WindowsInstaller;
11 + using WixToolset.Dtf.WindowsInstaller;
12 + using Xunit;
13 +
14 + public class InstanceTransformFixture
15 + {
16 + [Fact]
17 + public void CanBuildInstanceTransform()
18 + {
19 + var folder = TestData.Get("TestData", "InstanceTransform");
20 +
21 + using (var fs = new DisposableFileSystem())
22 + {
23 + var intermediateFolder = fs.GetFolder();
24 + var msiPath = Path.Combine(intermediateFolder, "bin", "test.msi");
25 + var wixpdbPath = Path.Combine(intermediateFolder, "bin", "test.wixpdb");
26 + var mstPath = Path.Combine(intermediateFolder, "iii.mst");
27 +
28 + var result = WixRunner.Execute(new[]
29 + {
30 + "build",
31 + Path.Combine(folder, "Package.wxs"),
32 + Path.Combine(folder, "PackageComponents.wxs"),
33 + "-loc", Path.Combine(folder, "Package.en-us.wxl"),
34 + "-bindpath", Path.Combine(folder, "data"),
35 + "-intermediateFolder", intermediateFolder,
36 + "-o", msiPath
37 + });
38 +
39 + result.AssertSuccess();
40 +
41 + var output = WindowsInstallerData.Load(wixpdbPath, false);
42 + var substorage = output.SubStorages.Single();
43 + Assert.Equal("I1", substorage.Name);
44 +
45 + var data = substorage.Data;
46 + WixAssert.CompareLineByLine(new[]
47 + {
48 + "_SummaryInformation",
49 + "Property",
50 + "Upgrade"
51 + }, data.Tables.Select(t => t.Name).ToArray());
52 +
53 + WixAssert.CompareLineByLine(new[]
54 + {
55 + "INSTANCEPROPERTY\tI1",
56 + "ProductName\tMsiPackage (Instance 1)",
57 + }, JoinRows(data.Tables["Property"]));
58 +
59 + WixAssert.CompareLineByLine(new[]
60 + {
61 + "{22222222-2222-2222-2222-222222222222}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED",
62 + "{11111111-1111-1111-1111-111111111111}\t\t1.0.0.0\t1033\t1\t0\t0",
63 + "{22222222-2222-2222-2222-222222222222}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED",
64 + "{11111111-1111-1111-1111-111111111111}\t1.0.0.0\t\t1033\t2\t0\t0",
65 + }, JoinRows(data.Tables["Upgrade"]));
66 +
67 + var names = Query.GetSubStorageNames(msiPath);
68 + Query.ExtractSubStorage(msiPath, "I1", mstPath);
69 +
70 + using (var db = new Database(msiPath, DatabaseOpenMode.Transact))
71 + {
72 + db.ApplyTransform(mstPath);
73 +
74 + var results = Query.QueryDatabase(db, new[] { "Property", "Upgrade" });
75 + var resultsWithoutProductCode = results.Where(s => !s.StartsWith("Property:ProductCode\t{")).ToArray();
76 + WixAssert.CompareLineByLine(new[]
77 + {
78 + "Property:ALLUSERS\t1",
79 + "Property:INSTANCEPROPERTY\tI1",
80 + "Property:Manufacturer\tExample Corporation",
81 + "Property:ProductLanguage\t1033",
82 + "Property:ProductName\tMsiPackage (Instance 1)",
83 + "Property:ProductVersion\t1.0.0.0",
84 + "Property:SecureCustomProperties\tINSTANCEPROPERTY;WIX_DOWNGRADE_DETECTED;WIX_UPGRADE_DETECTED",
85 + "Property:UpgradeCode\t{22222222-2222-2222-2222-222222222222}",
86 + "Upgrade:{22222222-2222-2222-2222-222222222222}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED",
87 + "Upgrade:{22222222-2222-2222-2222-222222222222}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED",
88 + }, resultsWithoutProductCode);
89 + }
90 + }
91 + }
92 +
93 + private static string[] JoinRows(Table table)
94 + {
95 + return table.Rows.Select(r => JoinFields(r.Fields)).ToArray();
96 +
97 + static string JoinFields(Field[] fields)
98 + {
99 + return String.Join('\t', fields.Select(f => f.ToString()));
100 + }
101 + }
102 + }
103 +}
src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs
+1 -63
@@ -2,12 +2,10 @@
2
3 namespace WixToolsetTest.CoreIntegration
4 {
5 - using System;
5 using System.IO;
6 using System.Linq;
8 - using Example.Extension;
9 - using WixInternal.TestSupport;
7 using WixInternal.Core.TestPackage;
8 + using WixInternal.TestSupport;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
@@ -598,56 +596,6 @@ namespace WixToolsetTest.CoreIntegration
596 }
597 }
598
601 - [Fact]
602 - public void CanBuildInstanceTransform()
603 - {
604 - var folder = TestData.Get(@"TestData\InstanceTransform");
605 -
606 - using (var fs = new DisposableFileSystem())
607 - {
608 - var intermediateFolder = fs.GetFolder();
609 -
610 - var result = WixRunner.Execute(new[]
611 - {
612 - "build",
613 - Path.Combine(folder, "Package.wxs"),
614 - Path.Combine(folder, "PackageComponents.wxs"),
615 - "-loc", Path.Combine(folder, "Package.en-us.wxl"),
616 - "-bindpath", Path.Combine(folder, "data"),
617 - "-intermediateFolder", intermediateFolder,
618 - "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
619 - });
620 -
621 - result.AssertSuccess();
622 -
623 - var output = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false);
624 - var substorage = output.SubStorages.Single();
625 - Assert.Equal("I1", substorage.Name);
626 -
627 - var data = substorage.Data;
628 - WixAssert.CompareLineByLine(new[]
629 - {
630 - "_SummaryInformation",
631 - "Property",
632 - "Upgrade"
633 - }, data.Tables.Select(t => t.Name).ToArray());
634 -
635 - WixAssert.CompareLineByLine(new[]
636 - {
637 - "INSTANCEPROPERTY\tI1",
638 - "ProductName\tMsiPackage (Instance 1)",
639 - }, JoinRows(data.Tables["Property"]));
640 -
641 - WixAssert.CompareLineByLine(new[]
642 - {
643 - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED",
644 - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t0\t0",
645 - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED",
646 - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t0\t0"
647 - }, JoinRows(data.Tables["Upgrade"]));
648 - }
649 - }
650 -
599 [Fact]
600 public void FailsBuildAtBindTimeForMissingEnsureTable()
601 {
@@ -680,15 +628,5 @@ namespace WixToolsetTest.CoreIntegration
628 Assert.False(File.Exists(msiPath));
629 }
630 }
683 -
684 - private static string[] JoinRows(Table table)
685 - {
686 - return table.Rows.Select(r => JoinFields(r.Fields)).ToArray();
687 -
688 - static string JoinFields(Field[] fields)
689 - {
690 - return String.Join('\t', fields.Select(f => f.ToString()));
691 - }
692 - }
631 }
632 }
src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs
+2 -4
@@ -1,13 +1,11 @@
1 <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 - <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3 -
4 -
2 + <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="11111111-1111-1111-1111-111111111111" Compressed="no" InstallerVersion="200" Scope="perMachine">
3 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
4
5 <Property Id="INSTANCEPROPERTY" Secure="yes" />
6
7 <InstanceTransforms Property="INSTANCEPROPERTY">
10 - <Instance Id="I1" ProductCode="*" ProductName="MsiPackage (Instance 1)" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" />
8 + <Instance Id="I1" ProductCode="*" ProductName="MsiPackage (Instance 1)" UpgradeCode="22222222-2222-2222-2222-222222222222" />
9 </InstanceTransforms>
10
11 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">