diff options
Diffstat (limited to 'src/WixToolset.Core/Bind/BindTransformCommand.cs')
-rw-r--r-- | src/WixToolset.Core/Bind/BindTransformCommand.cs | 473 |
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 | |||
3 | namespace 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 | } | ||