aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs470
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
3namespace 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}