diff options
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs')
-rw-r--r-- | src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs | 317 |
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 | |||
3 | namespace 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 | } | ||