aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs1282
1 files changed, 1282 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
new file mode 100644
index 00000000..2e2c5417
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -0,0 +1,1282 @@
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.Bind
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using WixToolset.Bind;
13 using WixToolset.Core.Bind;
14 using WixToolset.Core.WindowsInstaller.Databases;
15 using WixToolset.Data;
16 using WixToolset.Data.Bind;
17 using WixToolset.Data.Rows;
18 using WixToolset.Extensibility;
19 using WixToolset.Msi;
20
21 /// <summary>
22 /// Binds a databse.
23 /// </summary>
24 internal class BindDatabaseCommand
25 {
26 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
27 private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
28
29 public BindDatabaseCommand(IBindContext context, Validator validator)
30 {
31 this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions();
32
33 this.BindPaths = context.BindPaths;
34 this.CabbingThreadCount = context.CabbingThreadCount;
35 this.CabCachePath = context.CabCachePath;
36 this.Codepage = context.Codepage;
37 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
38 this.DelayedFields = context.DelayedFields;
39 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
40 this.Extensions = context.Extensions;
41 this.Output = context.IntermediateRepresentation;
42 this.OutputPath = context.OutputPath;
43 this.PdbFile = context.OutputPdbPath;
44 this.IntermediateFolder = context.IntermediateFolder;
45 this.Validator = validator;
46 this.WixVariableResolver = context.WixVariableResolver;
47
48 this.BackendExtensions = context.ExtensionManager.Create<IWindowsInstallerBackendExtension>();
49 }
50
51 private IEnumerable<BindPath> BindPaths { get; }
52
53 private int Codepage { get; }
54
55 private int CabbingThreadCount { get; }
56
57 private string CabCachePath { get; }
58
59 private CompressionLevel DefaultCompressionLevel { get; }
60
61 public IEnumerable<IDelayedField> DelayedFields { get; }
62
63 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; }
64
65 public bool DeltaBinaryPatch { get; set; }
66
67 private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; }
68
69 private IEnumerable<IBinderExtension> Extensions { get; }
70
71 private IEnumerable<InspectorExtension> InspectorExtensions { get; }
72
73 private string PdbFile { get; }
74
75 private Output Output { get; }
76
77 private string OutputPath { get; }
78
79 private bool SuppressAddingValidationRows { get; }
80
81 private bool SuppressLayout { get; }
82
83 private TableDefinitionCollection TableDefinitions { get; }
84
85 private string IntermediateFolder { get; }
86
87 private Validator Validator { get; }
88
89 private IBindVariableResolver WixVariableResolver { get; }
90
91 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
92
93 public IEnumerable<string> ContentFilePaths { get; private set; }
94
95 public void Execute()
96 {
97 List<FileTransfer> fileTransfers = new List<FileTransfer>();
98
99 HashSet<string> suppressedTableNames = new HashSet<string>();
100
101 // If there are any fields to resolve later, create the cache to populate during bind.
102 IDictionary<string, string> variableCache = null;
103 if (this.DelayedFields.Any())
104 {
105 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
106 }
107
108 this.LocalizeUI(this.Output.Tables);
109
110 // Process the summary information table before the other tables.
111 bool compressed;
112 bool longNames;
113 int installerVersion;
114 string modularizationGuid;
115 {
116 BindSummaryInfoCommand command = new BindSummaryInfoCommand();
117 command.Output = this.Output;
118 command.Execute();
119
120 compressed = command.Compressed;
121 longNames = command.LongNames;
122 installerVersion = command.InstallerVersion;
123 modularizationGuid = command.ModularizationGuid;
124 }
125
126 // Stop processing if an error previously occurred.
127 if (Messaging.Instance.EncounteredError)
128 {
129 return;
130 }
131
132 // Modularize identifiers and add tables with real streams to the import tables.
133 if (OutputType.Module == this.Output.Type)
134 {
135 // Gather all the suppress modularization identifiers
136 HashSet<string> suppressModularizationIdentifiers = null;
137 Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"];
138 if (null != wixSuppressModularizationTable)
139 {
140 suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0]));
141 }
142
143 foreach (Table table in this.Output.Tables)
144 {
145 table.Modularize(modularizationGuid, suppressModularizationIdentifiers);
146 }
147 }
148
149 // This must occur after all variables and source paths have been resolved and after modularization.
150 List<FileFacade> fileFacades;
151 {
152 GetFileFacadesCommand command = new GetFileFacadesCommand();
153 command.FileTable = this.Output.Tables["File"];
154 command.WixFileTable = this.Output.Tables["WixFile"];
155 command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"];
156 command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"];
157 command.Execute();
158
159 fileFacades = command.FileFacades;
160 }
161
162 ////if (OutputType.Patch == this.Output.Type)
163 ////{
164 //// foreach (SubStorage substorage in this.Output.SubStorages)
165 //// {
166 //// Output transform = substorage.Data;
167
168 //// ResolveFieldsCommand command = new ResolveFieldsCommand();
169 //// command.Tables = transform.Tables;
170 //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
171 //// command.FileManagerCore = this.FileManagerCore;
172 //// command.FileManagers = this.FileManagers;
173 //// command.SupportDelayedResolution = false;
174 //// command.TempFilesLocation = this.TempFilesLocation;
175 //// command.WixVariableResolver = this.WixVariableResolver;
176 //// command.Execute();
177
178 //// this.MergeUnrealTables(transform.Tables);
179 //// }
180 ////}
181
182 {
183 CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand();
184 command.PropertyTable = this.Output.Tables["Property"];
185 command.WixPropertyTable = this.Output.Tables["WixProperty"];
186 command.Execute();
187 }
188
189 if (Messaging.Instance.EncounteredError)
190 {
191 return;
192 }
193
194 // Add binder variables for all properties.
195 Table propertyTable = this.Output.Tables["Property"];
196 if (null != propertyTable)
197 {
198 foreach (PropertyRow propertyRow in propertyTable.Rows)
199 {
200 // Set the ProductCode if it is to be generated.
201 if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal))
202 {
203 propertyRow.Value = Common.GenerateGuid();
204
205 // Update the target ProductCode in any instance transforms.
206 foreach (SubStorage subStorage in this.Output.SubStorages)
207 {
208 Output subStorageOutput = subStorage.Data;
209 if (OutputType.Transform != subStorageOutput.Type)
210 {
211 continue;
212 }
213
214 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"];
215 foreach (Row row in instanceSummaryInformationTable.Rows)
216 {
217 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0))
218 {
219 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
220 break;
221 }
222 }
223 }
224 }
225
226 // Add the property name and value to the variableCache.
227 if (null != variableCache)
228 {
229 string key = String.Concat("property.", Common.Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property));
230 variableCache[key] = propertyRow.Value;
231 }
232 }
233 }
234
235 // Extract files that come from cabinet files (this does not extract files from merge modules).
236 {
237 ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand();
238 command.FilesWithEmbeddedFiles = this.ExpectedEmbeddedFiles;
239 command.Execute();
240 }
241
242 if (OutputType.Product == this.Output.Type)
243 {
244 // Retrieve files and their information from merge modules.
245 Table wixMergeTable = this.Output.Tables["WixMerge"];
246
247 if (null != wixMergeTable)
248 {
249 ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand();
250 command.FileFacades = fileFacades;
251 command.FileTable = this.Output.Tables["File"];
252 command.WixFileTable = this.Output.Tables["WixFile"];
253 command.WixMergeTable = wixMergeTable;
254 command.OutputInstallerVersion = installerVersion;
255 command.SuppressLayout = this.SuppressLayout;
256 command.TempFilesLocation = this.IntermediateFolder;
257 command.Execute();
258
259 fileFacades.AddRange(command.MergeModulesFileFacades);
260 }
261 }
262 else if (OutputType.Patch == this.Output.Type)
263 {
264 // Merge transform data into the output object.
265 IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
266
267 fileFacades.AddRange(filesFromTransform);
268 }
269
270 // stop processing if an error previously occurred
271 if (Messaging.Instance.EncounteredError)
272 {
273 return;
274 }
275
276 Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation());
277
278 // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table).
279 {
280 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
281 command.FileFacades = fileFacades;
282 command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule);
283 command.ModularizationGuid = modularizationGuid;
284 command.Output = this.Output;
285 command.OverwriteHash = true;
286 command.TableDefinitions = this.TableDefinitions;
287 command.VariableCache = variableCache;
288 command.Execute();
289 }
290
291 // Set generated component guids.
292 this.SetComponentGuids(this.Output);
293
294 // With the Component Guids set now we can create instance transforms.
295 this.CreateInstanceTransforms(this.Output);
296
297 this.ValidateComponentGuids(this.Output);
298
299 this.UpdateControlText(this.Output);
300
301 if (this.DelayedFields.Any())
302 {
303 ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand();
304 command.OutputType = this.Output.Type;
305 command.DelayedFields = this.DelayedFields;
306 command.ModularizationGuid = null;
307 command.VariableCache = variableCache;
308 command.Execute();
309 }
310
311 // Assign files to media.
312 RowDictionary<MediaRow> assignedMediaRows;
313 Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia;
314 IEnumerable<FileFacade> uncompressedFiles;
315 {
316 AssignMediaCommand command = new AssignMediaCommand();
317 command.FilesCompressed = compressed;
318 command.FileFacades = fileFacades;
319 command.Output = this.Output;
320 command.TableDefinitions = this.TableDefinitions;
321 command.Execute();
322
323 assignedMediaRows = command.MediaRows;
324 filesByCabinetMedia = command.FileFacadesByCabinetMedia;
325 uncompressedFiles = command.UncompressedFileFacades;
326 }
327
328 // Update file sequence.
329 this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows);
330
331 // stop processing if an error previously occurred
332 if (Messaging.Instance.EncounteredError)
333 {
334 return;
335 }
336
337 // Extended binder extensions can be called now that fields are resolved.
338 {
339 Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]);
340
341 foreach (IBinderExtension extension in this.Extensions)
342 {
343 extension.AfterResolvedFields(this.Output);
344 }
345
346 List<FileFacade> updatedFileFacades = new List<FileFacade>();
347
348 foreach (Row updatedFile in updatedFiles.Rows)
349 {
350 string updatedId = updatedFile.FieldAsString(0);
351
352 FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId));
353
354 updatedFileFacades.Add(updatedFacade);
355 }
356
357 if (updatedFileFacades.Any())
358 {
359 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
360 command.FileFacades = fileFacades;
361 command.UpdateFileFacades = updatedFileFacades;
362 command.ModularizationGuid = modularizationGuid;
363 command.Output = this.Output;
364 command.OverwriteHash = true;
365 command.TableDefinitions = this.TableDefinitions;
366 command.VariableCache = variableCache;
367 command.Execute();
368 }
369 }
370
371 // stop processing if an error previously occurred
372 if (Messaging.Instance.EncounteredError)
373 {
374 return;
375 }
376
377 Directory.CreateDirectory(this.IntermediateFolder);
378
379 if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch)
380 {
381 CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand();
382 command.FileFacades = fileFacades;
383 command.WixPatchIdTable = this.Output.Tables["WixPatchId"];
384 command.TempFilesLocation = this.IntermediateFolder;
385 command.Execute();
386 }
387
388 // create cabinet files and process uncompressed files
389 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
390 if (!this.SuppressLayout || OutputType.Module == this.Output.Type)
391 {
392 Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles());
393
394 var command = new CreateCabinetsCommand();
395 command.CabbingThreadCount = this.CabbingThreadCount;
396 command.CabCachePath = this.CabCachePath;
397 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
398 command.Output = this.Output;
399 command.BackendExtensions = this.BackendExtensions;
400 command.LayoutDirectory = layoutDirectory;
401 command.Compressed = compressed;
402 command.FileRowsByCabinet = filesByCabinetMedia;
403 command.ResolveMedia = this.ResolveMedia;
404 command.TableDefinitions = this.TableDefinitions;
405 command.TempFilesLocation = this.IntermediateFolder;
406 command.WixMediaTable = this.Output.Tables["WixMedia"];
407 command.Execute();
408
409 fileTransfers.AddRange(command.FileTransfers);
410 }
411
412 if (OutputType.Patch == this.Output.Type)
413 {
414 // copy output data back into the transforms
415 this.CopyToTransformData(this.Output);
416 }
417
418 // stop processing if an error previously occurred
419 if (Messaging.Instance.EncounteredError)
420 {
421 return;
422 }
423
424 // add back suppressed tables which must be present prior to merging in modules
425 if (OutputType.Product == this.Output.Type)
426 {
427 Table wixMergeTable = this.Output.Tables["WixMerge"];
428
429 if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count)
430 {
431 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
432 {
433 string sequenceTableName = sequence.ToString();
434 Table sequenceTable = this.Output.Tables[sequenceTableName];
435
436 if (null == sequenceTable)
437 {
438 sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]);
439 }
440
441 if (0 == sequenceTable.Rows.Count)
442 {
443 suppressedTableNames.Add(sequenceTableName);
444 }
445 }
446 }
447 }
448
449 //foreach (BinderExtension extension in this.Extensions)
450 //{
451 // extension.PostBind(this.Context);
452 //}
453
454 // generate database file
455 Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase());
456 string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath));
457 this.GenerateDatabase(this.Output, tempDatabaseFile, false, false);
458
459 FileTransfer transfer;
460 if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future
461 {
462 transfer.Built = true;
463 fileTransfers.Add(transfer);
464 }
465
466 // stop processing if an error previously occurred
467 if (Messaging.Instance.EncounteredError)
468 {
469 return;
470 }
471
472 // Output the output to a file
473 Pdb pdb = new Pdb();
474 pdb.Output = this.Output;
475 if (!String.IsNullOrEmpty(this.PdbFile))
476 {
477 pdb.Save(this.PdbFile);
478 }
479
480 // Merge modules.
481 if (OutputType.Product == this.Output.Type)
482 {
483 Messaging.Instance.OnMessage(WixVerboses.MergingModules());
484
485 MergeModulesCommand command = new MergeModulesCommand();
486 command.FileFacades = fileFacades;
487 command.Output = this.Output;
488 command.OutputPath = tempDatabaseFile;
489 command.SuppressedTableNames = suppressedTableNames;
490 command.Execute();
491
492 // stop processing if an error previously occurred
493 if (Messaging.Instance.EncounteredError)
494 {
495 return;
496 }
497 }
498
499 // inspect the MSI prior to running ICEs
500 //InspectorCore inspectorCore = new InspectorCore();
501 //foreach (InspectorExtension inspectorExtension in this.InspectorExtensions)
502 //{
503 // inspectorExtension.Core = inspectorCore;
504 // inspectorExtension.InspectDatabase(tempDatabaseFile, pdb);
505
506 // inspectorExtension.Core = null; // reset.
507 //}
508
509 if (Messaging.Instance.EncounteredError)
510 {
511 return;
512 }
513
514 // validate the output if there is an MSI validator
515 if (null != this.Validator)
516 {
517 Stopwatch stopwatch = Stopwatch.StartNew();
518
519 // set the output file for source line information
520 this.Validator.Output = this.Output;
521
522 Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase());
523
524 this.Validator.Validate(tempDatabaseFile);
525
526 stopwatch.Stop();
527 Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
528
529 // Stop processing if an error occurred.
530 if (Messaging.Instance.EncounteredError)
531 {
532 return;
533 }
534 }
535
536 // Process uncompressed files.
537 if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
538 {
539 var command = new ProcessUncompressedFilesCommand();
540 command.Compressed = compressed;
541 command.FileFacades = uncompressedFiles;
542 command.LayoutDirectory = layoutDirectory;
543 command.LongNamesInImage = longNames;
544 command.MediaRows = assignedMediaRows;
545 command.ResolveMedia = this.ResolveMedia;
546 command.DatabasePath = tempDatabaseFile;
547 command.WixMediaTable = this.Output.Tables["WixMedia"];
548 command.Execute();
549
550 fileTransfers.AddRange(command.FileTransfers);
551 }
552
553 this.FileTransfers = fileTransfers;
554 this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList();
555 }
556
557 /// <summary>
558 /// Localize dialogs and controls.
559 /// </summary>
560 /// <param name="tables">The tables to localize.</param>
561 private void LocalizeUI(TableIndexedCollection tables)
562 {
563 Table dialogTable = tables["Dialog"];
564 if (null != dialogTable)
565 {
566 foreach (Row row in dialogTable.Rows)
567 {
568 string dialog = (string)row[0];
569
570 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl))
571 {
572 if (CompilerConstants.IntegerNotSet != localizedControl.X)
573 {
574 row[1] = localizedControl.X;
575 }
576
577 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
578 {
579 row[2] = localizedControl.Y;
580 }
581
582 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
583 {
584 row[3] = localizedControl.Width;
585 }
586
587 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
588 {
589 row[4] = localizedControl.Height;
590 }
591
592 row[5] = (int)row[5] | localizedControl.Attributes;
593
594 if (!String.IsNullOrEmpty(localizedControl.Text))
595 {
596 row[6] = localizedControl.Text;
597 }
598 }
599 }
600 }
601
602 Table controlTable = tables["Control"];
603 if (null != controlTable)
604 {
605 foreach (Row row in controlTable.Rows)
606 {
607 string dialog = (string)row[0];
608 string control = (string)row[1];
609
610 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl))
611 {
612 if (CompilerConstants.IntegerNotSet != localizedControl.X)
613 {
614 row[3] = localizedControl.X.ToString();
615 }
616
617 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
618 {
619 row[4] = localizedControl.Y.ToString();
620 }
621
622 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
623 {
624 row[5] = localizedControl.Width.ToString();
625 }
626
627 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
628 {
629 row[6] = localizedControl.Height.ToString();
630 }
631
632 row[7] = (int)row[7] | localizedControl.Attributes;
633
634 if (!String.IsNullOrEmpty(localizedControl.Text))
635 {
636 row[9] = localizedControl.Text;
637 }
638 }
639 }
640 }
641 }
642
643 /// <summary>
644 /// Copy file data between transform substorages and the patch output object
645 /// </summary>
646 /// <param name="output">The output to bind.</param>
647 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
648 private IEnumerable<FileFacade> CopyFromTransformData(Output output)
649 {
650 var command = new CopyTransformDataCommand();
651 command.CopyOutFileRows = true;
652 command.Output = output;
653 command.TableDefinitions = this.TableDefinitions;
654 command.Execute();
655
656 return command.FileFacades;
657 }
658
659 /// <summary>
660 /// Copy file data between transform substorages and the patch output object
661 /// </summary>
662 /// <param name="output">The output to bind.</param>
663 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
664 private void CopyToTransformData(Output output)
665 {
666 var command = new CopyTransformDataCommand();
667 command.CopyOutFileRows = false;
668 command.Output = output;
669 command.TableDefinitions = this.TableDefinitions;
670 command.Execute();
671 }
672
673 private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows)
674 {
675 // Calculate sequence numbers and media disk id layout for all file media information objects.
676 if (OutputType.Module == outputType)
677 {
678 int lastSequence = 0;
679 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
680 {
681 facade.File.Sequence = ++lastSequence;
682 }
683 }
684 else
685 {
686 int lastSequence = 0;
687 MediaRow mediaRow = null;
688 Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>();
689
690 // sequence the non-patch-added files
691 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
692 {
693 if (null == mediaRow)
694 {
695 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
696 if (OutputType.Patch == outputType)
697 {
698 // patch Media cannot start at zero
699 lastSequence = mediaRow.LastSequence;
700 }
701 }
702 else if (mediaRow.DiskId != facade.WixFile.DiskId)
703 {
704 mediaRow.LastSequence = lastSequence;
705 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
706 }
707
708 if (0 < facade.WixFile.PatchGroup)
709 {
710 List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup];
711
712 if (null == patchGroup)
713 {
714 patchGroup = new List<FileFacade>();
715 patchGroups.Add(facade.WixFile.PatchGroup, patchGroup);
716 }
717
718 patchGroup.Add(facade);
719 }
720 else
721 {
722 facade.File.Sequence = ++lastSequence;
723 }
724 }
725
726 if (null != mediaRow)
727 {
728 mediaRow.LastSequence = lastSequence;
729 mediaRow = null;
730 }
731
732 // sequence the patch-added files
733 foreach (List<FileFacade> patchGroup in patchGroups.Values)
734 {
735 foreach (FileFacade facade in patchGroup)
736 {
737 if (null == mediaRow)
738 {
739 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
740 }
741 else if (mediaRow.DiskId != facade.WixFile.DiskId)
742 {
743 mediaRow.LastSequence = lastSequence;
744 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
745 }
746
747 facade.File.Sequence = ++lastSequence;
748 }
749 }
750
751 if (null != mediaRow)
752 {
753 mediaRow.LastSequence = lastSequence;
754 }
755 }
756 }
757
758 /// <summary>
759 /// Set the guids for components with generatable guids.
760 /// </summary>
761 /// <param name="output">Internal representation of the database to operate on.</param>
762 private void SetComponentGuids(Output output)
763 {
764 Table componentTable = output.Tables["Component"];
765 if (null != componentTable)
766 {
767 Hashtable registryKeyRows = null;
768 Hashtable directories = null;
769 Hashtable componentIdGenSeeds = null;
770 Dictionary<string, List<FileRow>> fileRows = null;
771
772 // find components with generatable guids
773 foreach (ComponentRow componentRow in componentTable.Rows)
774 {
775 // component guid will be generated
776 if ("*" == componentRow.Guid)
777 {
778 if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath)
779 {
780 Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers));
781 }
782 else if (componentRow.IsRegistryKeyPath)
783 {
784 if (null == registryKeyRows)
785 {
786 Table registryTable = output.Tables["Registry"];
787
788 registryKeyRows = new Hashtable(registryTable.Rows.Count);
789
790 foreach (Row registryRow in registryTable.Rows)
791 {
792 registryKeyRows.Add((string)registryRow[0], registryRow);
793 }
794 }
795
796 Row foundRow = registryKeyRows[componentRow.KeyPath] as Row;
797
798 string bitness = componentRow.Is64Bit ? "64" : String.Empty;
799 if (null != foundRow)
800 {
801 string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]);
802 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant();
803 }
804 }
805 else // must be a File KeyPath
806 {
807 // if the directory table hasn't been loaded into an indexed hash
808 // of directory ids to target names do that now.
809 if (null == directories)
810 {
811 Table directoryTable = output.Tables["Directory"];
812
813 int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0;
814
815 directories = new Hashtable(numDirectoryTableRows);
816
817 // get the target paths for all directories
818 if (null != directoryTable)
819 {
820 foreach (Row row in directoryTable.Rows)
821 {
822 // if the directory Id already exists, we will skip it here since
823 // checking for duplicate primary keys is done later when importing tables
824 // into database
825 if (directories.ContainsKey(row[0]))
826 {
827 continue;
828 }
829
830 string targetName = Common.GetName((string)row[2], false, true);
831 directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName));
832 }
833 }
834 }
835
836 // if the component id generation seeds have not been indexed
837 // from the WixDirectory table do that now.
838 if (null == componentIdGenSeeds)
839 {
840 Table wixDirectoryTable = output.Tables["WixDirectory"];
841
842 int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0;
843
844 componentIdGenSeeds = new Hashtable(numWixDirectoryRows);
845
846 // if there are any WixDirectory rows, build up the Component Guid
847 // generation seeds indexed by Directory/@Id.
848 if (null != wixDirectoryTable)
849 {
850 foreach (Row row in wixDirectoryTable.Rows)
851 {
852 componentIdGenSeeds.Add(row[0], (string)row[1]);
853 }
854 }
855 }
856
857 // if the file rows have not been indexed by File.Component yet
858 // then do that now
859 if (null == fileRows)
860 {
861 Table fileTable = output.Tables["File"];
862
863 int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0;
864
865 fileRows = new Dictionary<string, List<FileRow>>(numFileRows);
866
867 if (null != fileTable)
868 {
869 foreach (FileRow file in fileTable.Rows)
870 {
871 List<FileRow> files;
872 if (!fileRows.TryGetValue(file.Component, out files))
873 {
874 files = new List<FileRow>();
875 fileRows.Add(file.Component, files);
876 }
877
878 files.Add(file);
879 }
880 }
881 }
882
883 // validate component meets all the conditions to have a generated guid
884 List<FileRow> currentComponentFiles = fileRows[componentRow.Component];
885 int numFilesInComponent = currentComponentFiles.Count;
886 string path = null;
887
888 foreach (FileRow fileRow in currentComponentFiles)
889 {
890 if (fileRow.File == componentRow.KeyPath)
891 {
892 // calculate the key file's canonical target path
893 string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true);
894 string fileName = Common.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture);
895 path = Path.Combine(directoryPath, fileName);
896
897 // find paths that are not canonicalized
898 if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
899 path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
900 path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
901 path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
902 path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
903 path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
904 {
905 Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path));
906 }
907
908 // if component has more than one file, the key path must be versioned
909 if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version))
910 {
911 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers));
912 }
913 }
914 else
915 {
916 // not a key path, so it must be an unversioned file if component has more than one file
917 if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version))
918 {
919 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers));
920 }
921 }
922 }
923
924 // if the rules were followed, reward with a generated guid
925 if (!Messaging.Instance.EncounteredError)
926 {
927 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant();
928 }
929 }
930 }
931 }
932 }
933 }
934
935 /// <summary>
936 /// Creates instance transform substorages in the output.
937 /// </summary>
938 /// <param name="output">Output containing instance transform definitions.</param>
939 private void CreateInstanceTransforms(Output output)
940 {
941 // Create and add substorages for instance transforms.
942 Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"];
943 if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count)
944 {
945 string targetProductCode = null;
946 string targetUpgradeCode = null;
947 string targetProductVersion = null;
948
949 Table targetSummaryInformationTable = output.Tables["_SummaryInformation"];
950 Table targetPropertyTable = output.Tables["Property"];
951
952 // Get the data from target database
953 foreach (Row propertyRow in targetPropertyTable.Rows)
954 {
955 if ("ProductCode" == (string)propertyRow[0])
956 {
957 targetProductCode = (string)propertyRow[1];
958 }
959 else if ("ProductVersion" == (string)propertyRow[0])
960 {
961 targetProductVersion = (string)propertyRow[1];
962 }
963 else if ("UpgradeCode" == (string)propertyRow[0])
964 {
965 targetUpgradeCode = (string)propertyRow[1];
966 }
967 }
968
969 // Index the Instance Component Rows.
970 Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>();
971 Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"];
972 if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count)
973 {
974 foreach (Row row in targetInstanceComponentTable.Rows)
975 {
976 // Build up all the instances, we'll get the Components rows from the real Component table.
977 instanceComponentGuids.Add((string)row[0], null);
978 }
979
980 Table targetComponentTable = output.Tables["Component"];
981 foreach (ComponentRow componentRow in targetComponentTable.Rows)
982 {
983 string component = (string)componentRow[0];
984 if (instanceComponentGuids.ContainsKey(component))
985 {
986 instanceComponentGuids[component] = componentRow;
987 }
988 }
989 }
990
991 // Generate the instance transforms
992 foreach (Row instanceRow in wixInstanceTransformsTable.Rows)
993 {
994 string instanceId = (string)instanceRow[0];
995
996 Output instanceTransform = new Output(instanceRow.SourceLineNumbers);
997 instanceTransform.Type = OutputType.Transform;
998 instanceTransform.Codepage = output.Codepage;
999
1000 Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
1001 string targetPlatformAndLanguage = null;
1002
1003 foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows)
1004 {
1005 if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE
1006 {
1007 targetPlatformAndLanguage = (string)summaryInformationRow[1];
1008 }
1009
1010 // Copy the row's data to the transform.
1011 Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null);
1012 copyOfSummaryRow[0] = summaryInformationRow[0];
1013 copyOfSummaryRow[1] = summaryInformationRow[1];
1014 }
1015
1016 // Modify the appropriate properties.
1017 Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]);
1018
1019 // Change the ProductCode property
1020 string productCode = (string)instanceRow[2];
1021 if ("*" == productCode)
1022 {
1023 productCode = Common.GenerateGuid();
1024 }
1025
1026 Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1027 productCodeRow.Operation = RowOperation.Modify;
1028 productCodeRow.Fields[1].Modified = true;
1029 productCodeRow[0] = "ProductCode";
1030 productCodeRow[1] = productCode;
1031
1032 // Change the instance property
1033 Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1034 instanceIdRow.Operation = RowOperation.Modify;
1035 instanceIdRow.Fields[1].Modified = true;
1036 instanceIdRow[0] = (string)instanceRow[1];
1037 instanceIdRow[1] = instanceId;
1038
1039 if (null != instanceRow[3])
1040 {
1041 // Change the ProductName property
1042 Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1043 productNameRow.Operation = RowOperation.Modify;
1044 productNameRow.Fields[1].Modified = true;
1045 productNameRow[0] = "ProductName";
1046 productNameRow[1] = (string)instanceRow[3];
1047 }
1048
1049 if (null != instanceRow[4])
1050 {
1051 // Change the UpgradeCode property
1052 Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1053 upgradeCodeRow.Operation = RowOperation.Modify;
1054 upgradeCodeRow.Fields[1].Modified = true;
1055 upgradeCodeRow[0] = "UpgradeCode";
1056 upgradeCodeRow[1] = instanceRow[4];
1057
1058 // Change the Upgrade table
1059 Table targetUpgradeTable = output.Tables["Upgrade"];
1060 if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count)
1061 {
1062 string upgradeId = (string)instanceRow[4];
1063 Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]);
1064 foreach (Row row in targetUpgradeTable.Rows)
1065 {
1066 // In case they are upgrading other codes to this new product, leave the ones that don't match the
1067 // Product.UpgradeCode intact.
1068 if (targetUpgradeCode == (string)row[0])
1069 {
1070 Row upgradeRow = upgradeTable.CreateRow(null);
1071 upgradeRow.Operation = RowOperation.Add;
1072 upgradeRow.Fields[0].Modified = true;
1073 // I was hoping to be able to RowOperation.Modify, but that didn't appear to function.
1074 // upgradeRow.Fields[0].PreviousData = (string)row[0];
1075
1076 // Inserting a new Upgrade record with the updated UpgradeCode
1077 upgradeRow[0] = upgradeId;
1078 upgradeRow[1] = row[1];
1079 upgradeRow[2] = row[2];
1080 upgradeRow[3] = row[3];
1081 upgradeRow[4] = row[4];
1082 upgradeRow[5] = row[5];
1083 upgradeRow[6] = row[6];
1084
1085 // Delete the old row
1086 Row upgradeRemoveRow = upgradeTable.CreateRow(null);
1087 upgradeRemoveRow.Operation = RowOperation.Delete;
1088 upgradeRemoveRow[0] = row[0];
1089 upgradeRemoveRow[1] = row[1];
1090 upgradeRemoveRow[2] = row[2];
1091 upgradeRemoveRow[3] = row[3];
1092 upgradeRemoveRow[4] = row[4];
1093 upgradeRemoveRow[5] = row[5];
1094 upgradeRemoveRow[6] = row[6];
1095 }
1096 }
1097 }
1098 }
1099
1100 // If there are instance Components generate new GUIDs for them.
1101 if (0 < instanceComponentGuids.Count)
1102 {
1103 Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]);
1104 foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values)
1105 {
1106 string guid = targetComponentRow.Guid;
1107 if (!String.IsNullOrEmpty(guid))
1108 {
1109 Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers);
1110 instanceComponentRow.Operation = RowOperation.Modify;
1111 instanceComponentRow.Fields[1].Modified = true;
1112 instanceComponentRow[0] = targetComponentRow[0];
1113 instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture);
1114 instanceComponentRow[2] = targetComponentRow[2];
1115 instanceComponentRow[3] = targetComponentRow[3];
1116 instanceComponentRow[4] = targetComponentRow[4];
1117 instanceComponentRow[5] = targetComponentRow[5];
1118 }
1119 }
1120 }
1121
1122 // Update the summary information
1123 Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count);
1124 foreach (Row row in instanceSummaryInformationTable.Rows)
1125 {
1126 summaryRows[row[0]] = row;
1127
1128 if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
1129 {
1130 row[1] = targetPlatformAndLanguage;
1131 }
1132 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
1133 {
1134 row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode);
1135 }
1136 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
1137 {
1138 row[1] = 0;
1139 }
1140 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
1141 {
1142 row[1] = "4";
1143 }
1144 }
1145
1146 if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
1147 {
1148 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1149 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
1150 summaryRow[1] = targetPlatformAndLanguage;
1151 }
1152 else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
1153 {
1154 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1155 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
1156 summaryRow[1] = "0";
1157 }
1158 else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security))
1159 {
1160 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1161 summaryRow[0] = (int)SummaryInformation.Transform.Security;
1162 summaryRow[1] = "4";
1163 }
1164
1165 output.SubStorages.Add(new SubStorage(instanceId, instanceTransform));
1166 }
1167 }
1168 }
1169
1170 /// <summary>
1171 /// Validate that there are no duplicate GUIDs in the output.
1172 /// </summary>
1173 /// <remarks>
1174 /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a
1175 /// warning, as the conditions might be mutually exclusive.
1176 /// </remarks>
1177 private void ValidateComponentGuids(Output output)
1178 {
1179 Table componentTable = output.Tables["Component"];
1180 if (null != componentTable)
1181 {
1182 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
1183
1184 foreach (ComponentRow row in componentTable.Rows)
1185 {
1186 // we don't care about unmanaged components and if there's a * GUID remaining,
1187 // there's already an error that prevented it from being replaced with a real GUID.
1188 if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid)
1189 {
1190 bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
1191 bool allComponentsHaveConditions = thisComponentHasCondition;
1192
1193 if (componentGuidConditions.ContainsKey(row.Guid))
1194 {
1195 allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition;
1196
1197 if (allComponentsHaveConditions)
1198 {
1199 Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid));
1200 }
1201 else
1202 {
1203 Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid));
1204 }
1205 }
1206
1207 componentGuidConditions[row.Guid] = allComponentsHaveConditions;
1208 }
1209 }
1210 }
1211 }
1212
1213 /// <summary>
1214 /// Update Control and BBControl text by reading from files when necessary.
1215 /// </summary>
1216 /// <param name="output">Internal representation of the msi database to operate upon.</param>
1217 private void UpdateControlText(Output output)
1218 {
1219 UpdateControlTextCommand command = new UpdateControlTextCommand();
1220 command.BBControlTable = output.Tables["BBControl"];
1221 command.WixBBControlTable = output.Tables["WixBBControl"];
1222 command.ControlTable = output.Tables["Control"];
1223 command.WixControlTable = output.Tables["WixControl"];
1224 command.Execute();
1225 }
1226
1227 private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory)
1228 {
1229 string layout = null;
1230
1231 foreach (var extension in this.BackendExtensions)
1232 {
1233 layout = extension.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory);
1234 if (!String.IsNullOrEmpty(layout))
1235 {
1236 break;
1237 }
1238 }
1239
1240 // If no binder file manager resolved the layout, do the default behavior.
1241 if (String.IsNullOrEmpty(layout))
1242 {
1243 if (String.IsNullOrEmpty(mediaLayoutDirectory))
1244 {
1245 layout = layoutDirectory;
1246 }
1247 else if (Path.IsPathRooted(mediaLayoutDirectory))
1248 {
1249 layout = mediaLayoutDirectory;
1250 }
1251 else
1252 {
1253 layout = Path.Combine(layoutDirectory, mediaLayoutDirectory);
1254 }
1255 }
1256
1257 return layout;
1258 }
1259
1260 /// <summary>
1261 /// Creates the MSI/MSM/PCP database.
1262 /// </summary>
1263 /// <param name="output">Output to create database for.</param>
1264 /// <param name="databaseFile">The database file to create.</param>
1265 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
1266 /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
1267 private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
1268 {
1269 var command = new GenerateDatabaseCommand();
1270 command.Extensions = this.Extensions;
1271 command.Output = output;
1272 command.OutputPath = databaseFile;
1273 command.KeepAddedColumns = keepAddedColumns;
1274 command.UseSubDirectory = useSubdirectory;
1275 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
1276 command.TableDefinitions = this.TableDefinitions;
1277 command.TempFilesLocation = this.IntermediateFolder;
1278 command.Codepage = this.Codepage;
1279 command.Execute();
1280 }
1281 }
1282}