Implement transform decompiling and use it in patch filter tests
Rob Mensching committed
Sep 27, 2022 at 01:47 UTC
188952d84f5789128ddd32e7adf09e60899af43a
17 files changed
+390
-177
src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileResult.cs
+6
@@ -5,12 +5,18 @@ namespace WixToolset.Extensibility.Data
5
using System.Collections.Generic;
6
using System.Xml.Linq;
7
using WixToolset.Data;
8
+ using WixToolset.Data.WindowsInstaller;
9
10
/// <summary>
11
/// The result from decompiling a Windows Installer database.
12
/// </summary>
13
public interface IWindowsInstallerDecompileResult
14
{
15
+ /// <summary>
16
+ /// Decompiled <c>WindowsInstallerData</c>.
17
+ /// </summary>
18
+ WindowsInstallerData Data { get; set; }
19
+
20
/// <summary>
21
/// Decompiled document.
22
/// </summary>
src/test/burn/WixToolsetTest.BurnE2E/SlipstreamTests.cs
+1
-1
@@ -266,7 +266,7 @@ namespace WixToolsetTest.BurnE2E
266
packageBv1.VerifyTestRegistryRootDeleted();
267
}
268
269
- [RuntimeFact(Skip = "https://github.com/wixtoolset/issues/issues/6402")]
269
+ [RuntimeFact]
270
public void CanAutomaticallyPredetermineSlipstreamPatchesAtBuildTime()
271
{
272
var testRegistryValueA = "PackageA";
src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
+1
-1
@@ -94,7 +94,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
94
var exportBasePath = Path.Combine(this.IntermediateFolder, stageFolder);
95
var extractFilesFolder = Path.Combine(exportBasePath, "File");
96
97
- var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, OutputType.Product, exportBasePath, extractFilesFolder, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false);
97
+ var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, null, OutputType.Product, exportBasePath, extractFilesFolder, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false);
98
data = command.Execute();
99
}
100
src/wix/WixToolset.Core.WindowsInstaller/CommandLine/DecompilerSubcommand.cs
+47
-4
@@ -34,6 +34,8 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
34
35
private string ExportBasePath { get; set; }
36
37
+ private bool SaveAsData { get; set; }
38
+
39
private bool SuppressCustomTables { get; set; }
40
41
private bool SuppressDroppingEmptyTables { get; set; }
@@ -47,13 +49,14 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
49
return new CommandLineHelp("Converts a Windows Installer database back into source code.", "msi decompile [options] inputfile", new[]
50
{
51
new CommandLineHelpSwitch("-cub", "Optional path to a custom validation .CUBe file."),
52
+ new CommandLineHelpSwitch("-data", "Save output as data instead of as a source file."),
53
new CommandLineHelpSwitch("-sct", "Suppress decompiling custom tables."),
54
new CommandLineHelpSwitch("-sdet", "Suppress dropping empty tables."),
55
new CommandLineHelpSwitch("-sras", "Suppress relative action sequencing."),
56
new CommandLineHelpSwitch("-sui", "Suppress decompiling UI tables."),
57
new CommandLineHelpSwitch("-type", "Optional specify the input file type: msi or msm. If not specified, type will be inferred by file extension."),
58
new CommandLineHelpSwitch("-intermediateFolder", "Optional working folder. If not specified %TMP% will be used."),
56
- new CommandLineHelpSwitch("-out", "-o", "Path to output the decompiled .wxs file. If not specified, outputs next to inputfile"),
59
+ new CommandLineHelpSwitch("-out", "-o", "Path to output the decompiled output file. If not specified, outputs next to inputfile"),
60
new CommandLineHelpSwitch("-x", "Folder to export embedded binaries and icons to."),
61
});
62
}
@@ -77,7 +80,9 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
80
81
if (String.IsNullOrEmpty(this.OutputPath))
82
{
80
- this.OutputPath = Path.ChangeExtension(this.InputPath, ".wxs");
83
+ var defaultExtension = this.CalculateExtensionFromDecompileType(decompileType);
84
+
85
+ this.OutputPath = Path.ChangeExtension(this.InputPath, defaultExtension);
86
}
87
88
var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
@@ -92,7 +97,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
97
context.SymbolDefinitionCreator = creator;
98
context.OutputPath = this.OutputPath;
99
95
- context.ExtractFolder = this.ExportBasePath ?? this.IntermediateFolder;
100
+ context.ExtractFolder = this.ExportBasePath;
101
context.SuppressCustomTables = this.SuppressCustomTables;
102
context.SuppressDroppingEmptyTables = this.SuppressDroppingEmptyTables;
103
context.SuppressRelativeActionSequencing = this.SuppressRelativeActionSequencing;
@@ -106,7 +111,17 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
111
if (!this.Messaging.EncounteredError)
112
{
113
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(context.OutputPath)));
109
- result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces);
114
+ if (this.SaveAsData || result.Document == null)
115
+ {
116
+ using (var output = WixOutput.Create(context.OutputPath))
117
+ {
118
+ result.Data.Save(output);
119
+ }
120
+ }
121
+ else
122
+ {
123
+ result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces);
124
+ }
125
}
126
}
127
catch (WixException e)
@@ -129,6 +144,10 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
144
this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument);
145
return true;
146
147
+ case "data":
148
+ this.SaveAsData = true;
149
+ return true;
150
+
151
case "o":
152
case "out":
153
this.OutputPath = parser.GetNextArgumentAsFilePathOrError(argument, "output file");
@@ -197,9 +216,33 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
216
case ".msm":
217
decompileType = OutputType.Module;
218
break;
219
+
220
+ case "transform":
221
+ case "mst":
222
+ case ".mst":
223
+ decompileType = OutputType.Transform;
224
+ break;
225
}
226
227
return decompileType != OutputType.Unknown;
228
}
229
+
230
+ private string CalculateExtensionFromDecompileType(OutputType decompileType)
231
+ {
232
+ switch (decompileType)
233
+ {
234
+ case OutputType.Product:
235
+ return this.SaveAsData ? ".wixmsi" : ".wxs";
236
+
237
+ case OutputType.Module:
238
+ return this.SaveAsData ? ".wixmsm" : ".wxs";
239
+
240
+ case OutputType.Transform:
241
+ return ".wixmst";
242
+
243
+ default:
244
+ return this.SaveAsData ? ".wixdata" : ".wxs";
245
+ }
246
+ }
247
}
248
}
src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs
+1
-1
@@ -379,7 +379,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
379
{
380
if (!DataLoader.TryLoadWindowsInstallerData(path, out var data))
381
{
382
- var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, OutputType.Product, this.ExportBasePath, null, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false);
382
+ var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, null, OutputType.Product, this.ExportBasePath, null, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false);
383
data = unbindCommand.Execute();
384
}
385
src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+24
-18
@@ -21,12 +21,13 @@ 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
- 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, Database database, OutputType outputType, string exportBasePath, string extractFilesFolder, string intermediateFolder, bool enableDemodularization, bool skipSummaryInfo)
25
{
26
this.Messaging = messaging;
27
this.BackendHelper = backendHelper;
28
this.PathResolver = pathResolver;
29
this.DatabasePath = databasePath;
30
+ this.Database = database;
31
this.OutputType = outputType;
32
this.ExportBasePath = exportBasePath;
33
this.ExtractFilesFolder = extractFilesFolder;
@@ -45,21 +46,21 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
46
47
private Database Database { get; set; }
48
48
- public string DatabasePath { get; }
49
+ private string DatabasePath { get; }
50
50
- public OutputType OutputType { get; }
51
+ private OutputType OutputType { get; }
52
52
- public string ExportBasePath { get; }
53
+ private string ExportBasePath { get; }
54
54
- public string ExtractFilesFolder { get; }
55
+ private string ExtractFilesFolder { get; }
56
56
- public string IntermediateFolder { get; }
57
+ private string IntermediateFolder { get; }
58
58
- public bool EnableDemodularization { get; }
59
+ private bool EnableDemodularization { get; }
60
60
- public bool SkipSummaryInfo { get; }
61
+ private bool SkipSummaryInfo { get; }
62
62
- public TableDefinitionCollection TableDefinitions { get; }
63
+ private TableDefinitionCollection TableDefinitions { get; }
64
65
public bool AdminImage { get; private set; }
66
@@ -77,22 +78,24 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
78
Type = this.OutputType
79
};
80
81
+ Database database = null;
82
try
83
{
82
- using (var database = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
84
+ if (this.Database == null)
85
{
86
+ database = new Database(this.DatabasePath, OpenDatabase.ReadOnly);
87
this.Database = database;
88
+ }
89
86
- Directory.CreateDirectory(this.IntermediateFolder);
90
+ Directory.CreateDirectory(this.IntermediateFolder);
91
88
- data.Codepage = this.GetCodePage();
92
+ data.Codepage = this.GetCodePage();
93
90
- var modularizationGuid = this.ProcessTables(data, exportedFiles);
94
+ var modularizationGuid = this.ProcessTables(data, exportedFiles);
95
92
- var summaryInfo = this.ProcessSummaryInfo(data, modularizationGuid);
96
+ var summaryInfo = this.ProcessSummaryInfo(data, modularizationGuid);
97
94
- this.UpdateUnrealFileColumns(this.DatabasePath, data, summaryInfo, exportedFiles);
95
- }
98
+ this.UpdateUnrealFileColumns(this.DatabasePath, data, summaryInfo, exportedFiles);
99
}
100
catch (Win32Exception e)
101
{
@@ -103,6 +106,10 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
106
107
throw;
108
}
109
+ finally
110
+ {
111
+ database?.Dispose();
112
+ }
113
114
this.AdminImage = adminImage;
115
this.Data = data;
@@ -542,9 +549,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
549
source = Path.Combine(this.ExtractFilesFolder, fileRow.File);
550
}
551
}
545
- else
552
+ else if (componentDirectoryIndex.TryGetValue(fileRow.Component, out var directoryId)) // this can happen when unbinding an invalid MSI file or MST file with select table modifications.
553
{
547
- var directoryId = componentDirectoryIndex[fileRow.Component];
554
var relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, directoryId, fileRow.FileName, compressed: false, useLongName: summaryInformation.LongFilenames);
555
556
source = Path.Combine(databaseFolder, relativeFileLayoutPath);
src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
+178
-136
@@ -1,11 +1,8 @@
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
-#if TODO_KEEP_FOR_PATCH_UNBINDING_CONSIDERATION_IN_FUTURE
4
-
3
namespace WixToolset.Core.WindowsInstaller.Unbind
4
{
5
using System;
8
- using System.Collections;
6
using System.Collections.Generic;
7
using System.ComponentModel;
8
using System.Globalization;
@@ -19,10 +16,11 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
16
17
internal class UnbindTransformCommand
18
{
22
- public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string transformFile, string exportBasePath, string intermediateFolder)
19
+ public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, FileSystemManager fileSystemManager, string transformFile, string exportBasePath, string intermediateFolder)
20
{
21
this.Messaging = messaging;
22
this.BackendHelper = backendHelper;
23
+ this.PathResolver = pathResolver;
24
this.FileSystemManager = fileSystemManager;
25
this.TransformFile = transformFile;
26
this.ExportBasePath = exportBasePath;
@@ -35,6 +33,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
33
34
private IBackendHelper BackendHelper { get; }
35
36
+ private IPathResolver PathResolver { get; }
37
+
38
private FileSystemManager FileSystemManager { get; }
39
40
private string TransformFile { get; }
@@ -49,8 +49,10 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
49
50
public WindowsInstallerData Execute()
51
{
52
- var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile));
53
- transform.Type = OutputType.Transform;
52
+ var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile))
53
+ {
54
+ Type = OutputType.Transform
55
+ };
56
57
// get the summary information table
58
using (var summaryInformation = new SummaryInformation(this.TransformFile))
@@ -71,181 +73,215 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
73
}
74
75
// create a schema msi which hopefully matches the table schemas in the transform
74
- var schemaOutput = new WindowsInstallerData(null);
76
+ var schemaDatabasePath = Path.Combine(this.IntermediateFolder, "schema.msi");
77
+ var schemaData = this.CreateSchemaData(schemaDatabasePath);
78
+
79
+ // Bind the schema msi.
80
+ this.GenerateDatabase(schemaData);
81
+
82
+ var transformViewTable = this.OpenTransformViewForAddedAndModifiedRows(schemaDatabasePath);
83
+
84
+ var addedRows = this.CreatePlaceholdersForModifiedRowsAndIndexAddedRows(schemaData, transformViewTable);
85
+
86
+ // Re-bind the schema output with the placeholder rows over top the original schema database.
87
+ this.GenerateDatabase(schemaData);
88
+
89
+ this.PopulateTransformFromView(schemaDatabasePath, transform, transformViewTable, addedRows);
90
+
91
+ return transform;
92
+ }
93
+
94
+ private WindowsInstallerData CreateSchemaData(string schemaDatabasePath)
95
+ {
96
+ var schemaData = new WindowsInstallerData(new SourceLineNumber(schemaDatabasePath))
97
+ {
98
+ Type = OutputType.Product,
99
+ };
100
+
101
foreach (var tableDefinition in this.TableDefinitions)
102
{
103
// skip unreal tables and the Patch table
104
if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name)
105
{
80
- schemaOutput.EnsureTable(tableDefinition);
106
+ schemaData.EnsureTable(tableDefinition);
107
}
108
}
109
84
- var addedRows = new Dictionary<string, Row>();
85
- Table transformViewTable;
86
-
87
- // Bind the schema msi.
88
- var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi");
89
- this.GenerateDatabase(schemaOutput, msiDatabaseFile);
110
+ return schemaData;
111
+ }
112
91
- // Apply the transform to the database and retrieve the modifications.
92
- using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
113
+ private Table OpenTransformViewForAddedAndModifiedRows(string schemaDatabasePath)
114
+ {
115
+ // Apply the transform with the ViewTransform option to collect all the modifications.
116
+ using (var msiDatabase = this.ApplyTransformToSchemaDatabase(schemaDatabasePath, TransformErrorConditions.All | TransformErrorConditions.ViewTransform))
117
{
94
- // apply the transform with the ViewTransform option to collect all the modifications
95
- msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform);
96
-
118
// unbind the database
98
- var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true);
119
+ var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, schemaDatabasePath, msiDatabase, OutputType.Product, null, null, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true);
120
var transformViewOutput = unbindCommand.Execute();
121
101
- // index the added and possibly modified rows (added rows may also appears as modified rows)
102
- transformViewTable = transformViewOutput.Tables["_TransformView"];
103
- var modifiedRows = new Hashtable();
104
- foreach (var row in transformViewTable.Rows)
105
- {
106
- var tableName = (string)row[0];
107
- var columnName = (string)row[1];
108
- var primaryKeys = (string)row[2];
109
-
110
- if ("INSERT" == columnName)
111
- {
112
- var index = String.Concat(tableName, ':', primaryKeys);
122
+ return transformViewOutput.Tables["_TransformView"];
123
+ }
124
+ }
125
114
- addedRows.Add(index, null);
115
- }
116
- else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row
117
- {
118
- var index = String.Concat(tableName, ':', primaryKeys);
126
+ private Dictionary<string, Row> CreatePlaceholdersForModifiedRowsAndIndexAddedRows(WindowsInstallerData schemaData, Table transformViewTable)
127
+ {
128
+ // Index the added and possibly modified rows (added rows may also appears as modified rows).
129
+ var addedRows = new Dictionary<string, Row>();
130
+ var modifiedRows = new Dictionary<string, TableNameWithPrimaryKeys>();
131
120
- modifiedRows[index] = row;
121
- }
122
- }
132
+ foreach (var row in transformViewTable.Rows)
133
+ {
134
+ var tableName = row.FieldAsString(0);
135
+ var columnName = row.FieldAsString(1);
136
+ var primaryKeys = row.FieldAsString(2);
137
124
- // create placeholder rows for modified rows to make the transform insert the updated values when its applied
125
- foreach (Row row in modifiedRows.Values)
138
+ if ("INSERT" == columnName)
139
{
127
- var tableName = (string)row[0];
128
- var columnName = (string)row[1];
129
- var primaryKeys = (string)row[2];
140
+ var index = String.Concat(tableName, ':', primaryKeys);
141
142
+ addedRows.Add(index, null);
143
+ }
144
+ else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row
145
+ {
146
var index = String.Concat(tableName, ':', primaryKeys);
147
133
- // ignore information for added rows
134
- if (!addedRows.ContainsKey(index))
148
+ if (!modifiedRows.ContainsKey(index))
149
{
136
- var table = schemaOutput.Tables[tableName];
137
- this.CreateRow(table, primaryKeys, true);
150
+ modifiedRows.Add(index, new TableNameWithPrimaryKeys { TableName = tableName, PrimaryKeys = primaryKeys });
151
}
152
}
153
}
154
142
- // Re-bind the schema output with the placeholder rows.
143
- this.GenerateDatabase(schemaOutput, msiDatabaseFile);
144
-
145
- // apply the transform to the database and retrieve the modifications
146
- using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
155
+ // Create placeholder rows for modified rows to make the transform insert the updated values when its applied.
156
+ foreach (var kvp in modifiedRows)
157
{
148
- try
149
- {
150
- // apply the transform
151
- msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All);
158
+ var index = kvp.Key;
159
+ var tableNameWithPrimaryKey = kvp.Value;
160
153
- // commit the database to guard against weird errors with streams
154
- msiDatabase.Commit();
155
- }
156
- catch (Win32Exception ex)
161
+ // Ignore added rows.
162
+ if (!addedRows.ContainsKey(index))
163
{
158
- if (0x65B == ex.NativeErrorCode)
159
- {
160
- // this commonly happens when the transform was built
161
- // against a database schema different from the internal
162
- // table definitions
163
- throw new WixException(ErrorMessages.TransformSchemaMismatch());
164
- }
164
+ var table = schemaData.Tables[tableNameWithPrimaryKey.TableName];
165
+ this.CreateRow(table, tableNameWithPrimaryKey.PrimaryKeys, setRequiredFields: true);
166
}
167
+ }
168
+
169
+ return addedRows;
170
+ }
171
+
172
+ private void PopulateTransformFromView(string schemaDatabasePath, WindowsInstallerData transform, Table transformViewTable, Dictionary<string, Row> addedRows)
173
+ {
174
+ WindowsInstallerData output;
175
+ // Apply the transform to the database and retrieve the modifications
176
+ using (var database = this.ApplyTransformToSchemaDatabase(schemaDatabasePath, TransformErrorConditions.All))
177
+ {
178
179
// unbind the database
168
- var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true);
169
- var output = unbindCommand.Execute();
180
+ var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, schemaDatabasePath, database, OutputType.Product, this.ExportBasePath, null, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true);
181
+ output = unbindCommand.Execute();
182
+ }
183
171
- // index all the rows to easily find modified rows
172
- var rows = new Dictionary<string, Row>();
173
- foreach (var table in output.Tables)
184
+ // index all the rows to easily find modified rows
185
+ var rows = new Dictionary<string, Row>();
186
+ foreach (var table in output.Tables)
187
+ {
188
+ foreach (var row in table.Rows)
189
{
175
- foreach (var row in table.Rows)
176
- {
177
- rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row);
178
- }
190
+ rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row);
191
}
192
+ }
193
181
- // process the _TransformView rows into transform rows
182
- foreach (var row in transformViewTable.Rows)
183
- {
184
- var tableName = (string)row[0];
185
- var columnName = (string)row[1];
186
- var primaryKeys = (string)row[2];
194
+ // process the _TransformView rows into transform rows
195
+ foreach (var row in transformViewTable.Rows)
196
+ {
197
+ var tableName = row.FieldAsString(0);
198
+ var columnName = row.FieldAsString(1);
199
+ var primaryKeys = row.FieldAsString(2);
200
188
- var table = transform.EnsureTable(this.TableDefinitions[tableName]);
201
+ var table = transform.EnsureTable(this.TableDefinitions[tableName]);
202
190
- if ("CREATE" == columnName) // added table
191
- {
192
- table.Operation = TableOperation.Add;
193
- }
194
- else if ("DELETE" == columnName) // deleted row
195
- {
196
- var deletedRow = this.CreateRow(table, primaryKeys, false);
197
- deletedRow.Operation = RowOperation.Delete;
198
- }
199
- else if ("DROP" == columnName) // dropped table
200
- {
201
- table.Operation = TableOperation.Drop;
202
- }
203
- else if ("INSERT" == columnName) // added row
204
- {
205
- var index = String.Concat(tableName, ':', primaryKeys);
206
- var addedRow = rows[index];
207
- addedRow.Operation = RowOperation.Add;
208
- table.Rows.Add(addedRow);
209
- }
210
- else if (null != primaryKeys) // modified row
203
+ if ("CREATE" == columnName) // added table
204
+ {
205
+ table.Operation = TableOperation.Add;
206
+ }
207
+ else if ("DELETE" == columnName) // deleted row
208
+ {
209
+ var deletedRow = this.CreateRow(table, primaryKeys, false);
210
+ deletedRow.Operation = RowOperation.Delete;
211
+ }
212
+ else if ("DROP" == columnName) // dropped table
213
+ {
214
+ table.Operation = TableOperation.Drop;
215
+ }
216
+ else if ("INSERT" == columnName) // added row
217
+ {
218
+ var index = String.Concat(tableName, ':', primaryKeys);
219
+ var addedRow = rows[index];
220
+ addedRow.Operation = RowOperation.Add;
221
+ table.Rows.Add(addedRow);
222
+ }
223
+ else if (null != primaryKeys) // modified row
224
+ {
225
+ var index = String.Concat(tableName, ':', primaryKeys);
226
+
227
+ // the _TransformView table includes information for added rows
228
+ // that looks like modified rows so it sometimes needs to be ignored
229
+ if (!addedRows.ContainsKey(index))
230
{
212
- var index = String.Concat(tableName, ':', primaryKeys);
231
+ var modifiedRow = rows[index];
232
214
- // the _TransformView table includes information for added rows
215
- // that looks like modified rows so it sometimes needs to be ignored
216
- if (!addedRows.ContainsKey(index))
233
+ // mark the field as modified
234
+ var indexOfModifiedValue = -1;
235
+ for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i)
236
{
218
- var modifiedRow = rows[index];
219
-
220
- // mark the field as modified
221
- var indexOfModifiedValue = -1;
222
- for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i)
237
+ if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal))
238
{
224
- if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal))
225
- {
226
- indexOfModifiedValue = i;
227
- break;
228
- }
239
+ indexOfModifiedValue = i;
240
+ break;
241
}
230
- modifiedRow.Fields[indexOfModifiedValue].Modified = true;
242
+ }
243
+ modifiedRow.Fields[indexOfModifiedValue].Modified = true;
244
232
- // move the modified row into the transform the first time its encountered
233
- if (RowOperation.None == modifiedRow.Operation)
234
- {
235
- modifiedRow.Operation = RowOperation.Modify;
236
- table.Rows.Add(modifiedRow);
237
- }
245
+ // move the modified row into the transform the first time its encountered
246
+ if (RowOperation.None == modifiedRow.Operation)
247
+ {
248
+ modifiedRow.Operation = RowOperation.Modify;
249
+ table.Rows.Add(modifiedRow);
250
}
251
}
240
- else // added column
241
- {
242
- var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal));
243
- column.Added = true;
244
- }
252
+ }
253
+ else // added column
254
+ {
255
+ var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal));
256
+ column.Added = true;
257
}
258
}
259
+ }
260
248
- return transform;
261
+ private Database ApplyTransformToSchemaDatabase(string schemaDatabasePath, TransformErrorConditions transformConditions)
262
+ {
263
+ var msiDatabase = new Database(schemaDatabasePath, OpenDatabase.Transact);
264
+
265
+ try
266
+ {
267
+ // apply the transform
268
+ msiDatabase.ApplyTransform(this.TransformFile, transformConditions);
269
+
270
+ // commit the database to guard against weird errors with streams
271
+ msiDatabase.Commit();
272
+ }
273
+ catch (Win32Exception ex)
274
+ {
275
+ if (0x65B == ex.NativeErrorCode)
276
+ {
277
+ // this commonly happens when the transform was built
278
+ // against a database schema different from the internal
279
+ // table definitions
280
+ throw new WixException(ErrorMessages.TransformSchemaMismatch());
281
+ }
282
+ }
283
+
284
+ return msiDatabase;
285
}
286
287
/// <summary>
@@ -305,11 +341,17 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
341
return row;
342
}
343
308
- private void GenerateDatabase(WindowsInstallerData output, string databaseFile)
344
+ private void GenerateDatabase(WindowsInstallerData data)
345
{
310
- var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false);
346
+ var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, data, data.SourceLineNumbers.FileName, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false);
347
command.Execute();
348
}
349
+
350
+ private class TableNameWithPrimaryKeys
351
+ {
352
+ public string TableName { get; set; }
353
+
354
+ public string PrimaryKeys { get; set; }
355
+ }
356
}
357
}
315
-#endif
src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompileResult.cs
+3
@@ -5,10 +5,13 @@ namespace WixToolset.Core.WindowsInstaller
5
using System.Collections.Generic;
6
using System.Xml.Linq;
7
using WixToolset.Data;
8
+ using WixToolset.Data.WindowsInstaller;
9
using WixToolset.Extensibility.Data;
10
11
internal class WindowsInstallerDecompileResult : IWindowsInstallerDecompileResult
12
{
13
+ public WindowsInstallerData Data { get; set; }
14
+
15
public XDocument Document { get; set; }
16
17
public IList<string> ExtractedFilePaths { get; set; }
src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs
+34
-3
@@ -3,13 +3,15 @@
3
namespace WixToolset.Core.WindowsInstaller
4
{
5
using System;
6
- using System.ComponentModel.Design;
6
+ using System.Collections.Generic;
7
using System.IO;
8
using System.Linq;
9
+ using WixToolset.Core.WindowsInstaller.Bind;
10
using WixToolset.Core.WindowsInstaller.Decompile;
11
using WixToolset.Core.WindowsInstaller.Unbind;
12
using WixToolset.Data;
13
using WixToolset.Data.WindowsInstaller;
14
+ using WixToolset.Extensibility;
15
using WixToolset.Extensibility.Data;
16
using WixToolset.Extensibility.Services;
17
@@ -22,12 +24,15 @@ namespace WixToolset.Core.WindowsInstaller
24
{
25
this.ServiceProvider = serviceProvider;
26
this.Messaging = serviceProvider.GetService<IMessaging>();
27
+ this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
28
}
29
30
private IServiceProvider ServiceProvider { get; }
31
32
private IMessaging Messaging { get; }
33
34
+ private IExtensionManager ExtensionManager { get; }
35
+
36
public IWindowsInstallerDecompileResult Decompile(IWindowsInstallerDecompileContext context)
37
{
38
if (context.SymbolDefinitionCreator == null)
@@ -62,7 +67,7 @@ namespace WixToolset.Core.WindowsInstaller
67
private IWindowsInstallerDecompileResult Execute(IWindowsInstallerDecompileContext context)
68
{
69
// Delete the directory and its files to prevent cab extraction failure due to an existing file.
65
- if (Directory.Exists(context.ExtractFolder))
70
+ if (!String.IsNullOrEmpty(context.ExtractFolder) && Directory.Exists(context.ExtractFolder))
71
{
72
Directory.Delete(context.ExtractFolder, true);
73
}
@@ -71,11 +76,23 @@ namespace WixToolset.Core.WindowsInstaller
76
77
var pathResolver = context.ServiceProvider.GetService<IPathResolver>();
78
79
+ if (context.DecompileType == OutputType.Transform)
80
+ {
81
+ return this.DecompileTransform(context, backendHelper, pathResolver);
82
+ }
83
+ else
84
+ {
85
+ return this.DecompileDatabase(context, backendHelper, pathResolver);
86
+ }
87
+ }
88
+
89
+ private IWindowsInstallerDecompileResult DecompileDatabase(IWindowsInstallerDecompileContext context, IWindowsInstallerBackendHelper backendHelper, IPathResolver pathResolver)
90
+ {
91
var extractFilesFolder = context.SuppressExtractCabinets || (String.IsNullOrEmpty(context.CabinetExtractFolder) && String.IsNullOrEmpty(context.ExtractFolder)) ? null :
92
String.IsNullOrEmpty(context.CabinetExtractFolder) ? Path.Combine(context.ExtractFolder, "File") : context.CabinetExtractFolder;
93
94
var outputType = context.TreatProductAsModule ? OutputType.Module : context.DecompileType;
78
- var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, pathResolver, context.DecompilePath, outputType, context.ExtractFolder, extractFilesFolder, context.IntermediateFolder, enableDemodularization: true, skipSummaryInfo: false);
95
+ var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, pathResolver, context.DecompilePath, null, outputType, context.ExtractFolder, extractFilesFolder, context.IntermediateFolder, enableDemodularization: true, skipSummaryInfo: false);
96
var output = unbindCommand.Execute();
97
var extractedFilePaths = unbindCommand.ExportedFiles;
98
@@ -84,10 +101,24 @@ namespace WixToolset.Core.WindowsInstaller
101
var document = decompiler.Decompile(output);
102
103
var result = context.ServiceProvider.GetService<IWindowsInstallerDecompileResult>();
104
+ result.Data = output;
105
result.Document = document;
106
result.Platform = GetPlatformFromOutput(output);
107
result.ExtractedFilePaths = extractedFilePaths.ToList();
108
+ return result;
109
+ }
110
+
111
+ private IWindowsInstallerDecompileResult DecompileTransform(IWindowsInstallerDecompileContext context, IWindowsInstallerBackendHelper backendHelper, IPathResolver pathResolver)
112
+ {
113
+ var fileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>();
114
115
+ var fileSystemManager = new FileSystemManager(fileSystemExtensions);
116
+
117
+ var unbindCommand = new UnbindTransformCommand(this.Messaging, backendHelper, pathResolver, fileSystemManager, context.DecompilePath, context.ExtractFolder, context.IntermediateFolder);
118
+ var output = unbindCommand.Execute();
119
+
120
+ var result = context.ServiceProvider.GetService<IWindowsInstallerDecompileResult>();
121
+ result.Data = output;
122
return result;
123
}
124
src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
+75
-11
@@ -18,6 +18,8 @@ namespace WixToolsetTest.CoreIntegration
18
using WixToolset.Core.TestPackage;
19
using WixToolset.Data;
20
using WixToolset.Data.Burn;
21
+ using WixToolset.Data.WindowsInstaller;
22
+ using WixToolset.Dtf.Compression.Cab;
23
using Xunit;
24
25
public class PatchFixture : IDisposable
@@ -395,9 +397,9 @@ namespace WixToolsetTest.CoreIntegration
397
}
398
399
[Fact]
398
- public void CanBuildPatchWithFiltering()
400
+ public void CanBuildPatchWithFileFiltering()
401
{
400
- var sourceFolder = TestData.Get(@"TestData", "PatchFamilyFilter");
402
+ var sourceFolder = TestData.Get(@"TestData", "PatchFamilyFileFilter");
403
404
using (var fs = new DisposableFileSystem())
405
{
@@ -406,23 +408,50 @@ namespace WixToolsetTest.CoreIntegration
408
409
var patchPath = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(this.templateBaselinePdb), Path.GetDirectoryName(this.templateUpdatePdb) });
410
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);
411
+ var mainTransform = ExtractBaselinePatch(patchPath, "RTM.1", baseFolder);
412
+ Assert.Null(mainTransform.Tables["Registry"]);
413
+ var fileRow = mainTransform.Tables["File"].Rows.Single();
414
+ Assert.Equal("a.txt", fileRow.FieldAsString(0));
415
+ Assert.Equal(152, fileRow.FieldAsInteger(3));
416
415
- var cab = Path.Combine(baseFolder, "foo.cab");
416
- Query.ExtractStream(patchPath, "foo.cab", cab);
417
+ var pairedTransform = ExtractBaselinePatch(patchPath, "#RTM.1", baseFolder);
418
+ fileRow = mainTransform.Tables["File"].Rows.Single();
419
+ Assert.Equal("a.txt", fileRow.FieldAsString(0));
420
+ Assert.Equal(152, fileRow.FieldAsInteger(3));
421
418
- var files = Query.GetCabinetFiles(cab);
422
+ var files = ExtractFilesFromPatchCabinet(patchPath, "foo.cab", baseFolder);
423
var file = files.Single();
420
- WixAssert.StringEqual("a.txt", file.Name);
424
var contents = file.OpenText().ReadToEnd();
425
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);
426
}
427
}
428
429
+ [Fact]
430
+ public void CanBuildPatchWithRegistryFiltering()
431
+ {
432
+ var sourceFolder = TestData.Get(@"TestData", "PatchFamilyRegistryFilter");
433
+
434
+ using (var fs = new DisposableFileSystem())
435
+ {
436
+ var baseFolder = fs.GetFolder();
437
+ var tempFolderPatch = Path.Combine(baseFolder, "patch");
438
+
439
+ var patchPath = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(this.templateBaselinePdb), Path.GetDirectoryName(this.templateUpdatePdb) }, warningsAsErrors: false);
440
+
441
+ var mainTransform = ExtractBaselinePatch(patchPath, "RTM.1", baseFolder);
442
+ Assert.Null(mainTransform.Tables["File"]);
443
+ var row = mainTransform.Tables["Registry"].Rows.Single();
444
+ Assert.Equal("regUty0zLJ5uYhRlGzmOzENKmnAtno", row.FieldAsString(0));
445
+ Assert.Equal("1.0.1", row.FieldAsString(4));
446
+
447
+ var pairedTransform = ExtractBaselinePatch(patchPath, "#RTM.1", baseFolder);
448
+ Assert.Null(pairedTransform.Tables["File"]);
449
+
450
+ var files = ExtractFilesFromPatchCabinet(patchPath, "foo.cab", baseFolder);
451
+ Assert.Empty(files);
452
+ }
453
+ }
454
+
455
private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB, IEnumerable<string> bindpaths = null)
456
{
457
var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
@@ -552,6 +581,41 @@ namespace WixToolsetTest.CoreIntegration
581
Assert.Equal(0, proc.ExitCode);
582
}
583
584
+ private static WindowsInstallerData DecompileMst(string transformPath, string baseFolder)
585
+ {
586
+ var outputPath = Path.ChangeExtension(transformPath, ".wixmst");
587
+
588
+ var args = new List<string>
589
+ {
590
+ "msi", "decompile",
591
+ transformPath,
592
+ "-intermediateFolder", Path.Combine(baseFolder),
593
+ "-o", outputPath,
594
+ };
595
+
596
+ var result = WixRunner.Execute(args.ToArray());
597
+
598
+ result.AssertSuccess();
599
+
600
+ return WindowsInstallerData.Load(outputPath);
601
+ }
602
+
603
+ private static WindowsInstallerData ExtractBaselinePatch(string patchPath, string substorageName, string baseFolder)
604
+ {
605
+ var mstPath = Path.Combine(baseFolder, substorageName, substorageName + ".mst");
606
+ Query.ExtractSubStorage(patchPath, substorageName, mstPath);
607
+
608
+ return DecompileMst(mstPath, baseFolder);
609
+ }
610
+
611
+ private static CabFileInfo[] ExtractFilesFromPatchCabinet(string patchPath, string cabinetName, string baseFolder)
612
+ {
613
+ var cab = Path.Combine(baseFolder, cabinetName);
614
+ Query.ExtractStream(patchPath, cabinetName, cab);
615
+
616
+ return Query.GetCabinetFiles(cab);
617
+ }
618
+
619
private static XDocument GetExtractPatchXml(string path)
620
{
621
var buffer = new StringBuilder(65535);
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFileFilter/.data/Av1.0.0.txt
renamed
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFileFilter/.data/Av1.0.1.txt
renamed
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFileFilter/.data/Bv1.0.0.txt
renamed
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFileFilter/.data/Bv1.0.1.txt
renamed
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFileFilter/Package.wxs
renamed
+3
-1
@@ -1,4 +1,5 @@
1
-<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
1
+<!-- TODO: DELETE ???
2
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3
<Package Name="~Test Package" Version="$(var.V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="e703bf17-4765-444c-91fd-88550fa681d4" Scope="perMachine" ProductCode="e703bf17-4765-444c-91fd-88550fa681d4">
4
5
@@ -26,3 +27,4 @@
27
</ComponentGroup>
28
</Fragment>
29
</Wix>
30
+-->
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFileFilter/Patch.wxs
renamed
+1
-1
@@ -10,7 +10,7 @@
10
11
<Fragment>
12
<PatchFamily Id="SamplePatchFamily" Version="$(var.V)" Supersede="yes">
13
- <ComponentRef Id="A" />
13
+ <ComponentRef Id="a.txt" />
14
</PatchFamily>
15
</Fragment>
16
</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyRegistryFilter/Patch.wxs
new
+16
@@ -0,0 +1,16 @@
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" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
6
+ </Media>
7
+
8
+ <PatchFamilyRef Id="SamplePatchFamily" />
9
+ </Patch>
10
+
11
+ <Fragment>
12
+ <PatchFamily Id="SamplePatchFamily" Version="$(var.V)" Supersede="yes">
13
+ <ComponentRef Id="regUty0zLJ5uYhRlGzmOzENKmnAtno" />
14
+ </PatchFamily>
15
+ </Fragment>
16
+</Wix>