diff options
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs')
-rw-r--r-- | src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..425d1f9c --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs | |||
@@ -0,0 +1,470 @@ | |||
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.Databases | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Globalization; | ||
8 | using System.IO; | ||
9 | using WixToolset.Data; | ||
10 | using WixToolset.Extensibility; | ||
11 | using WixToolset.Msi; | ||
12 | using WixToolset.Core.Native; | ||
13 | |||
14 | internal class BindTransformCommand | ||
15 | { | ||
16 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
17 | |||
18 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
19 | |||
20 | public string TempFilesLocation { private get; set; } | ||
21 | |||
22 | public Output Transform { private get; set; } | ||
23 | |||
24 | public string OutputPath { private get; set; } | ||
25 | |||
26 | public void Execute() | ||
27 | { | ||
28 | int transformFlags = 0; | ||
29 | |||
30 | Output targetOutput = new Output(null); | ||
31 | Output updatedOutput = new Output(null); | ||
32 | |||
33 | // TODO: handle added columns | ||
34 | |||
35 | // to generate a localized transform, both the target and updated | ||
36 | // databases need to have the same code page. the only reason to | ||
37 | // set different code pages is to support localized primary key | ||
38 | // columns, but that would only support deleting rows. if this | ||
39 | // becomes necessary, define a PreviousCodepage property on the | ||
40 | // Output class and persist this throughout transform generation. | ||
41 | targetOutput.Codepage = this.Transform.Codepage; | ||
42 | updatedOutput.Codepage = this.Transform.Codepage; | ||
43 | |||
44 | // remove certain Property rows which will be populated from summary information values | ||
45 | string targetUpgradeCode = null; | ||
46 | string updatedUpgradeCode = null; | ||
47 | |||
48 | Table propertyTable = this.Transform.Tables["Property"]; | ||
49 | if (null != propertyTable) | ||
50 | { | ||
51 | for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) | ||
52 | { | ||
53 | Row row = propertyTable.Rows[i]; | ||
54 | |||
55 | if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) | ||
56 | { | ||
57 | propertyTable.Rows.RemoveAt(i); | ||
58 | |||
59 | if ("UpgradeCode" == (string)row[0]) | ||
60 | { | ||
61 | updatedUpgradeCode = (string)row[1]; | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
68 | Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
69 | Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
70 | Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
71 | |||
72 | // process special summary information values | ||
73 | foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows) | ||
74 | { | ||
75 | if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) | ||
76 | { | ||
77 | // convert from a web name if provided | ||
78 | string codePage = (string)row.Fields[1].Data; | ||
79 | if (null == codePage) | ||
80 | { | ||
81 | codePage = "0"; | ||
82 | } | ||
83 | else | ||
84 | { | ||
85 | codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); | ||
86 | } | ||
87 | |||
88 | string previousCodePage = (string)row.Fields[1].PreviousData; | ||
89 | if (null == previousCodePage) | ||
90 | { | ||
91 | previousCodePage = "0"; | ||
92 | } | ||
93 | else | ||
94 | { | ||
95 | previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); | ||
96 | } | ||
97 | |||
98 | Row targetCodePageRow = targetSummaryInfo.CreateRow(null); | ||
99 | targetCodePageRow[0] = 1; // PID_CODEPAGE | ||
100 | targetCodePageRow[1] = previousCodePage; | ||
101 | |||
102 | Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); | ||
103 | updatedCodePageRow[0] = 1; // PID_CODEPAGE | ||
104 | updatedCodePageRow[1] = codePage; | ||
105 | } | ||
106 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || | ||
107 | (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
108 | { | ||
109 | // the target language | ||
110 | string[] propertyData = ((string)row[1]).Split(';'); | ||
111 | string lang = 2 == propertyData.Length ? propertyData[1] : "0"; | ||
112 | |||
113 | Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; | ||
114 | Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; | ||
115 | |||
116 | Row productLanguageRow = tempPropertyTable.CreateRow(null); | ||
117 | productLanguageRow[0] = "ProductLanguage"; | ||
118 | productLanguageRow[1] = lang; | ||
119 | |||
120 | // set the platform;language on the MSI to be generated | ||
121 | Row templateRow = tempSummaryInfo.CreateRow(null); | ||
122 | templateRow[0] = 7; // PID_TEMPLATE | ||
123 | templateRow[1] = (string)row[1]; | ||
124 | } | ||
125 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
126 | { | ||
127 | string[] propertyData = ((string)row[1]).Split(';'); | ||
128 | |||
129 | Row targetProductCodeRow = targetPropertyTable.CreateRow(null); | ||
130 | targetProductCodeRow[0] = "ProductCode"; | ||
131 | targetProductCodeRow[1] = propertyData[0].Substring(0, 38); | ||
132 | |||
133 | Row targetProductVersionRow = targetPropertyTable.CreateRow(null); | ||
134 | targetProductVersionRow[0] = "ProductVersion"; | ||
135 | targetProductVersionRow[1] = propertyData[0].Substring(38); | ||
136 | |||
137 | Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); | ||
138 | updatedProductCodeRow[0] = "ProductCode"; | ||
139 | updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); | ||
140 | |||
141 | Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); | ||
142 | updatedProductVersionRow[0] = "ProductVersion"; | ||
143 | updatedProductVersionRow[1] = propertyData[1].Substring(38); | ||
144 | |||
145 | // UpgradeCode is optional and may not exists in the target | ||
146 | // or upgraded databases, so do not include a null-valued | ||
147 | // UpgradeCode property. | ||
148 | |||
149 | targetUpgradeCode = propertyData[2]; | ||
150 | if (!String.IsNullOrEmpty(targetUpgradeCode)) | ||
151 | { | ||
152 | Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); | ||
153 | targetUpgradeCodeRow[0] = "UpgradeCode"; | ||
154 | targetUpgradeCodeRow[1] = targetUpgradeCode; | ||
155 | |||
156 | // If the target UpgradeCode is specified, an updated | ||
157 | // UpgradeCode is required. | ||
158 | if (String.IsNullOrEmpty(updatedUpgradeCode)) | ||
159 | { | ||
160 | updatedUpgradeCode = targetUpgradeCode; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | if (!String.IsNullOrEmpty(updatedUpgradeCode)) | ||
165 | { | ||
166 | Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); | ||
167 | updatedUpgradeCodeRow[0] = "UpgradeCode"; | ||
168 | updatedUpgradeCodeRow[1] = updatedUpgradeCode; | ||
169 | } | ||
170 | } | ||
171 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
172 | { | ||
173 | transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); | ||
174 | } | ||
175 | else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) | ||
176 | { | ||
177 | // PID_LASTPRINTED should be null for transforms | ||
178 | row.Operation = RowOperation.None; | ||
179 | } | ||
180 | else | ||
181 | { | ||
182 | // add everything else as is | ||
183 | Row targetRow = targetSummaryInfo.CreateRow(null); | ||
184 | targetRow[0] = row[0]; | ||
185 | targetRow[1] = row[1]; | ||
186 | |||
187 | Row updatedRow = updatedSummaryInfo.CreateRow(null); | ||
188 | updatedRow[0] = row[0]; | ||
189 | updatedRow[1] = row[1]; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | // Validate that both databases have an UpgradeCode if the | ||
194 | // authoring transform will validate the UpgradeCode; otherwise, | ||
195 | // MsiCreateTransformSummaryinfo() will fail with 1620. | ||
196 | if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && | ||
197 | (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) | ||
198 | { | ||
199 | Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired()); | ||
200 | } | ||
201 | |||
202 | string emptyFile = null; | ||
203 | |||
204 | foreach (Table table in this.Transform.Tables) | ||
205 | { | ||
206 | // Ignore unreal tables when building transforms except the _Stream table. | ||
207 | // These tables are ignored when generating the database so there is no reason | ||
208 | // to process them here. | ||
209 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
210 | { | ||
211 | continue; | ||
212 | } | ||
213 | |||
214 | // process table operations | ||
215 | switch (table.Operation) | ||
216 | { | ||
217 | case TableOperation.Add: | ||
218 | updatedOutput.EnsureTable(table.Definition); | ||
219 | break; | ||
220 | case TableOperation.Drop: | ||
221 | targetOutput.EnsureTable(table.Definition); | ||
222 | continue; | ||
223 | default: | ||
224 | targetOutput.EnsureTable(table.Definition); | ||
225 | updatedOutput.EnsureTable(table.Definition); | ||
226 | break; | ||
227 | } | ||
228 | |||
229 | // process row operations | ||
230 | foreach (Row row in table.Rows) | ||
231 | { | ||
232 | switch (row.Operation) | ||
233 | { | ||
234 | case RowOperation.Add: | ||
235 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
236 | updatedTable.Rows.Add(row); | ||
237 | continue; | ||
238 | case RowOperation.Delete: | ||
239 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
240 | targetTable.Rows.Add(row); | ||
241 | |||
242 | // fill-in non-primary key values | ||
243 | foreach (Field field in row.Fields) | ||
244 | { | ||
245 | if (!field.Column.PrimaryKey) | ||
246 | { | ||
247 | if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) | ||
248 | { | ||
249 | field.Data = field.Column.MinValue; | ||
250 | } | ||
251 | else if (ColumnType.Object == field.Column.Type) | ||
252 | { | ||
253 | if (null == emptyFile) | ||
254 | { | ||
255 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
256 | } | ||
257 | |||
258 | field.Data = emptyFile; | ||
259 | } | ||
260 | else | ||
261 | { | ||
262 | field.Data = "0"; | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | continue; | ||
267 | } | ||
268 | |||
269 | // Assure that the file table's sequence is populated | ||
270 | if ("File" == table.Name) | ||
271 | { | ||
272 | foreach (Row fileRow in table.Rows) | ||
273 | { | ||
274 | if (null == fileRow[7]) | ||
275 | { | ||
276 | if (RowOperation.Add == fileRow.Operation) | ||
277 | { | ||
278 | Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); | ||
279 | break; | ||
280 | } | ||
281 | |||
282 | // Set to 1 to prevent invalid IDT file from being generated | ||
283 | fileRow[7] = 1; | ||
284 | } | ||
285 | } | ||
286 | } | ||
287 | |||
288 | // process modified and unmodified rows | ||
289 | bool modifiedRow = false; | ||
290 | Row targetRow = new Row(null, table.Definition); | ||
291 | Row updatedRow = row; | ||
292 | for (int i = 0; i < row.Fields.Length; i++) | ||
293 | { | ||
294 | Field updatedField = row.Fields[i]; | ||
295 | |||
296 | if (updatedField.Modified) | ||
297 | { | ||
298 | // set a different value in the target row to ensure this value will be modified during transform generation | ||
299 | if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) | ||
300 | { | ||
301 | if (null == updatedField.Data || 1 != (int)updatedField.Data) | ||
302 | { | ||
303 | targetRow[i] = 1; | ||
304 | } | ||
305 | else | ||
306 | { | ||
307 | targetRow[i] = 2; | ||
308 | } | ||
309 | } | ||
310 | else if (ColumnType.Object == updatedField.Column.Type) | ||
311 | { | ||
312 | if (null == emptyFile) | ||
313 | { | ||
314 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
315 | } | ||
316 | |||
317 | targetRow[i] = emptyFile; | ||
318 | } | ||
319 | else | ||
320 | { | ||
321 | if ("0" != (string)updatedField.Data) | ||
322 | { | ||
323 | targetRow[i] = "0"; | ||
324 | } | ||
325 | else | ||
326 | { | ||
327 | targetRow[i] = "1"; | ||
328 | } | ||
329 | } | ||
330 | |||
331 | modifiedRow = true; | ||
332 | } | ||
333 | else if (ColumnType.Object == updatedField.Column.Type) | ||
334 | { | ||
335 | ObjectField objectField = (ObjectField)updatedField; | ||
336 | |||
337 | // create an empty file for comparing against | ||
338 | if (null == objectField.PreviousData) | ||
339 | { | ||
340 | if (null == emptyFile) | ||
341 | { | ||
342 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
343 | } | ||
344 | |||
345 | targetRow[i] = emptyFile; | ||
346 | modifiedRow = true; | ||
347 | } | ||
348 | else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data)) | ||
349 | { | ||
350 | targetRow[i] = objectField.PreviousData; | ||
351 | modifiedRow = true; | ||
352 | } | ||
353 | } | ||
354 | else // unmodified | ||
355 | { | ||
356 | if (null != updatedField.Data) | ||
357 | { | ||
358 | targetRow[i] = updatedField.Data; | ||
359 | } | ||
360 | } | ||
361 | } | ||
362 | |||
363 | // modified rows and certain special rows go in the target and updated msi databases | ||
364 | if (modifiedRow || | ||
365 | ("Property" == table.Name && | ||
366 | ("ProductCode" == (string)row[0] || | ||
367 | "ProductLanguage" == (string)row[0] || | ||
368 | "ProductVersion" == (string)row[0] || | ||
369 | "UpgradeCode" == (string)row[0]))) | ||
370 | { | ||
371 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
372 | targetTable.Rows.Add(targetRow); | ||
373 | |||
374 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
375 | updatedTable.Rows.Add(updatedRow); | ||
376 | } | ||
377 | } | ||
378 | } | ||
379 | |||
380 | //foreach (BinderExtension extension in this.Extensions) | ||
381 | //{ | ||
382 | // extension.PostBind(this.Context); | ||
383 | //} | ||
384 | |||
385 | // Any errors encountered up to this point can cause errors during generation. | ||
386 | if (Messaging.Instance.EncounteredError) | ||
387 | { | ||
388 | return; | ||
389 | } | ||
390 | |||
391 | string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
392 | string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); | ||
393 | string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); | ||
394 | |||
395 | try | ||
396 | { | ||
397 | if (!String.IsNullOrEmpty(emptyFile)) | ||
398 | { | ||
399 | using (FileStream fileStream = File.Create(emptyFile)) | ||
400 | { | ||
401 | } | ||
402 | } | ||
403 | |||
404 | this.GenerateDatabase(targetOutput, targetDatabaseFile, false); | ||
405 | this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true); | ||
406 | |||
407 | // make sure the directory exists | ||
408 | Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); | ||
409 | |||
410 | // create the transform file | ||
411 | using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) | ||
412 | { | ||
413 | using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) | ||
414 | { | ||
415 | if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) | ||
416 | { | ||
417 | updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); | ||
418 | } | ||
419 | else | ||
420 | { | ||
421 | Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); | ||
422 | } | ||
423 | } | ||
424 | } | ||
425 | } | ||
426 | finally | ||
427 | { | ||
428 | if (!String.IsNullOrEmpty(emptyFile)) | ||
429 | { | ||
430 | File.Delete(emptyFile); | ||
431 | } | ||
432 | } | ||
433 | } | ||
434 | |||
435 | private bool CompareFiles(string targetFile, string updatedFile) | ||
436 | { | ||
437 | bool? compared = null; | ||
438 | foreach (var extension in this.Extensions) | ||
439 | { | ||
440 | compared = extension.CompareFiles(targetFile, updatedFile); | ||
441 | if (compared.HasValue) | ||
442 | { | ||
443 | break; | ||
444 | } | ||
445 | } | ||
446 | |||
447 | if (!compared.HasValue) | ||
448 | { | ||
449 | throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. | ||
450 | } | ||
451 | |||
452 | return compared.Value; | ||
453 | } | ||
454 | |||
455 | private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns) | ||
456 | { | ||
457 | var command = new GenerateDatabaseCommand(); | ||
458 | command.Codepage = output.Codepage; | ||
459 | command.Extensions = this.Extensions; | ||
460 | command.KeepAddedColumns = keepAddedColumns; | ||
461 | command.Output = output; | ||
462 | command.OutputPath = outputPath; | ||
463 | command.TableDefinitions = this.TableDefinitions; | ||
464 | command.TempFilesLocation = this.TempFilesLocation; | ||
465 | command.SuppressAddingValidationRows = true; | ||
466 | command.UseSubDirectory = true; | ||
467 | command.Execute(); | ||
468 | } | ||
469 | } | ||
470 | } | ||