aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs317
1 files changed, 317 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
new file mode 100644
index 00000000..c0eda9c7
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
@@ -0,0 +1,317 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Unbind
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Text.RegularExpressions;
13 using WixToolset.Core.Native;
14 using WixToolset.Core.WindowsInstaller.Databases;
15 using WixToolset.Data;
16 using WixToolset.Data.Rows;
17 using WixToolset.Extensibility;
18 using WixToolset.Msi;
19
20 internal class UnbindTransformCommand
21 {
22 public UnbindTransformCommand(Messaging messaging, string transformFile, string exportBasePath, string intermediateFolder)
23 {
24 this.Messaging = messaging;
25 this.TransformFile = transformFile;
26 this.ExportBasePath = exportBasePath;
27 this.IntermediateFolder = intermediateFolder;
28
29 this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions();
30 }
31
32 private Messaging Messaging { get; }
33
34 private string TransformFile { get; }
35
36 private string ExportBasePath { get; }
37
38 private string IntermediateFolder { get; }
39
40 private TableDefinitionCollection TableDefinitions { get; }
41
42 private string EmptyFile { get; set; }
43
44 public Output Execute()
45 {
46 Output transform = new Output(new SourceLineNumber(this.TransformFile));
47 transform.Type = OutputType.Transform;
48
49 // get the summary information table
50 using (SummaryInformation summaryInformation = new SummaryInformation(this.TransformFile))
51 {
52 Table table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
53
54 for (int i = 1; 19 >= i; i++)
55 {
56 string value = summaryInformation.GetProperty(i);
57
58 if (0 < value.Length)
59 {
60 Row row = table.CreateRow(transform.SourceLineNumbers);
61 row[0] = i;
62 row[1] = value;
63 }
64 }
65 }
66
67 // create a schema msi which hopefully matches the table schemas in the transform
68 Output schemaOutput = new Output(null);
69 string msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi");
70 foreach (TableDefinition tableDefinition in this.TableDefinitions)
71 {
72 // skip unreal tables and the Patch table
73 if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name)
74 {
75 schemaOutput.EnsureTable(tableDefinition);
76 }
77 }
78
79 Hashtable addedRows = new Hashtable();
80 Table transformViewTable;
81
82 // Bind the schema msi.
83 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
84
85 // apply the transform to the database and retrieve the modifications
86 using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
87 {
88 // apply the transform with the ViewTransform option to collect all the modifications
89 msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform);
90
91 // unbind the database
92 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true);
93 Output transformViewOutput = unbindCommand.Execute();
94
95 // index the added and possibly modified rows (added rows may also appears as modified rows)
96 transformViewTable = transformViewOutput.Tables["_TransformView"];
97 Hashtable modifiedRows = new Hashtable();
98 foreach (Row row in transformViewTable.Rows)
99 {
100 string tableName = (string)row[0];
101 string columnName = (string)row[1];
102 string primaryKeys = (string)row[2];
103
104 if ("INSERT" == columnName)
105 {
106 string index = String.Concat(tableName, ':', primaryKeys);
107
108 addedRows.Add(index, null);
109 }
110 else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row
111 {
112 string index = String.Concat(tableName, ':', primaryKeys);
113
114 modifiedRows[index] = row;
115 }
116 }
117
118 // create placeholder rows for modified rows to make the transform insert the updated values when its applied
119 foreach (Row row in modifiedRows.Values)
120 {
121 string tableName = (string)row[0];
122 string columnName = (string)row[1];
123 string primaryKeys = (string)row[2];
124
125 string index = String.Concat(tableName, ':', primaryKeys);
126
127 // ignore information for added rows
128 if (!addedRows.Contains(index))
129 {
130 Table table = schemaOutput.Tables[tableName];
131 this.CreateRow(table, primaryKeys, true);
132 }
133 }
134 }
135
136 // Re-bind the schema output with the placeholder rows.
137 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
138
139 // apply the transform to the database and retrieve the modifications
140 using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
141 {
142 try
143 {
144 // apply the transform
145 msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All);
146
147 // commit the database to guard against weird errors with streams
148 msiDatabase.Commit();
149 }
150 catch (Win32Exception ex)
151 {
152 if (0x65B == ex.NativeErrorCode)
153 {
154 // this commonly happens when the transform was built
155 // against a database schema different from the internal
156 // table definitions
157 throw new WixException(WixErrors.TransformSchemaMismatch());
158 }
159 }
160
161 // unbind the database
162 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true);
163 Output output = unbindCommand.Execute();
164
165 // index all the rows to easily find modified rows
166 Hashtable rows = new Hashtable();
167 foreach (Table table in output.Tables)
168 {
169 foreach (Row row in table.Rows)
170 {
171 rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row);
172 }
173 }
174
175 // process the _TransformView rows into transform rows
176 foreach (Row row in transformViewTable.Rows)
177 {
178 string tableName = (string)row[0];
179 string columnName = (string)row[1];
180 string primaryKeys = (string)row[2];
181
182 Table table = transform.EnsureTable(this.TableDefinitions[tableName]);
183
184 if ("CREATE" == columnName) // added table
185 {
186 table.Operation = TableOperation.Add;
187 }
188 else if ("DELETE" == columnName) // deleted row
189 {
190 Row deletedRow = this.CreateRow(table, primaryKeys, false);
191 deletedRow.Operation = RowOperation.Delete;
192 }
193 else if ("DROP" == columnName) // dropped table
194 {
195 table.Operation = TableOperation.Drop;
196 }
197 else if ("INSERT" == columnName) // added row
198 {
199 string index = String.Concat(tableName, ':', primaryKeys);
200 Row addedRow = (Row)rows[index];
201 addedRow.Operation = RowOperation.Add;
202 table.Rows.Add(addedRow);
203 }
204 else if (null != primaryKeys) // modified row
205 {
206 string index = String.Concat(tableName, ':', primaryKeys);
207
208 // the _TransformView table includes information for added rows
209 // that looks like modified rows so it sometimes needs to be ignored
210 if (!addedRows.Contains(index))
211 {
212 Row modifiedRow = (Row)rows[index];
213
214 // mark the field as modified
215 int indexOfModifiedValue = -1;
216 for (int i = 0; i < modifiedRow.TableDefinition.Columns.Count; ++i)
217 {
218 if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal))
219 {
220 indexOfModifiedValue = i;
221 break;
222 }
223 }
224 modifiedRow.Fields[indexOfModifiedValue].Modified = true;
225
226 // move the modified row into the transform the first time its encountered
227 if (RowOperation.None == modifiedRow.Operation)
228 {
229 modifiedRow.Operation = RowOperation.Modify;
230 table.Rows.Add(modifiedRow);
231 }
232 }
233 }
234 else // added column
235 {
236 ColumnDefinition column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal));
237 column.Added = true;
238 }
239 }
240 }
241
242 return transform;
243 }
244
245 private void GenerateDatabase(Output output, string databaseFile)
246 {
247 var command = new GenerateDatabaseCommand();
248 command.Extensions = Array.Empty<IBinderExtension>();
249 command.Output = output;
250 command.OutputPath = databaseFile;
251 command.KeepAddedColumns = true;
252 command.UseSubDirectory = false;
253 command.SuppressAddingValidationRows = true;
254 command.TableDefinitions = this.TableDefinitions;
255 command.TempFilesLocation = this.IntermediateFolder;
256 command.Codepage = -1;
257 command.Execute();
258 }
259
260 /// <summary>
261 /// Create a deleted or modified row.
262 /// </summary>
263 /// <param name="table">The table containing the row.</param>
264 /// <param name="primaryKeys">The primary keys of the row.</param>
265 /// <param name="setRequiredFields">Option to set all required fields with placeholder values.</param>
266 /// <returns>The new row.</returns>
267 private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields)
268 {
269 Row row = table.CreateRow(null);
270
271 string[] primaryKeyParts = primaryKeys.Split('\t');
272 int primaryKeyPartIndex = 0;
273
274 for (int i = 0; i < table.Definition.Columns.Count; i++)
275 {
276 ColumnDefinition columnDefinition = table.Definition.Columns[i];
277
278 if (columnDefinition.PrimaryKey)
279 {
280 if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
281 {
282 row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture);
283 }
284 else
285 {
286 row[i] = primaryKeyParts[primaryKeyPartIndex++];
287 }
288 }
289 else if (setRequiredFields)
290 {
291 if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
292 {
293 row[i] = 1;
294 }
295 else if (ColumnType.Object == columnDefinition.Type)
296 {
297 if (null == this.EmptyFile)
298 {
299 this.EmptyFile = Path.GetTempFileName() + ".empty";
300 using (FileStream fileStream = File.Create(this.EmptyFile))
301 {
302 }
303 }
304
305 row[i] = this.EmptyFile;
306 }
307 else
308 {
309 row[i] = "1";
310 }
311 }
312 }
313
314 return row;
315 }
316 }
317}