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