aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Binder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Binder.cs')
-rw-r--r--src/WixToolset.Core/Binder.cs686
1 files changed, 686 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs
new file mode 100644
index 00000000..18ad2d62
--- /dev/null
+++ b/src/WixToolset.Core/Binder.cs
@@ -0,0 +1,686 @@
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
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 System.Reflection;
13 using WixToolset.Bind;
14 using WixToolset.Data;
15 using WixToolset.Data.Rows;
16 using WixToolset.Extensibility;
17 using WixToolset.Msi;
18
19 // TODO: (4.0) Refactor so that these don't need to be copied.
20 // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs
21 [Flags]
22 internal enum WixFileSearchAttributes
23 {
24 Default = 0x001,
25 MinVersionInclusive = 0x002,
26 MaxVersionInclusive = 0x004,
27 MinSizeInclusive = 0x008,
28 MaxSizeInclusive = 0x010,
29 MinDateInclusive = 0x020,
30 MaxDateInclusive = 0x040,
31 WantVersion = 0x080,
32 WantExists = 0x100,
33 IsDirectory = 0x200,
34 }
35
36 [Flags]
37 internal enum WixRegistrySearchAttributes
38 {
39 Raw = 0x01,
40 Compatible = 0x02,
41 ExpandEnvironmentVariables = 0x04,
42 WantValue = 0x08,
43 WantExists = 0x10,
44 Win64 = 0x20,
45 }
46
47 internal enum WixComponentSearchAttributes
48 {
49 KeyPath = 0x1,
50 State = 0x2,
51 WantDirectory = 0x4,
52 }
53
54 [Flags]
55 internal enum WixProductSearchAttributes
56 {
57 Version = 0x1,
58 Language = 0x2,
59 State = 0x4,
60 Assignment = 0x8,
61 UpgradeCode = 0x10,
62 }
63
64 /// <summary>
65 /// Binder of the WiX toolset.
66 /// </summary>
67 public sealed class Binder
68 {
69 private BinderCore core;
70 private BinderFileManagerCore fileManagerCore;
71 private List<IBinderExtension> extensions;
72 private List<IBinderFileManager> fileManagers;
73 private List<InspectorExtension> inspectorExtensions;
74
75 public Binder()
76 {
77 this.DefaultCompressionLevel = CompressionLevel.High;
78
79 this.BindPaths = new List<BindPath>();
80 this.TargetBindPaths = new List<BindPath>();
81 this.UpdatedBindPaths = new List<BindPath>();
82
83 this.extensions = new List<IBinderExtension>();
84 this.fileManagers = new List<IBinderFileManager>();
85 this.inspectorExtensions = new List<InspectorExtension>();
86
87 this.Ices = new List<string>();
88 this.SuppressIces = new List<string>();
89 }
90
91 public string ContentsFile { private get; set; }
92
93 public string OutputsFile { private get; set; }
94
95 public string BuiltOutputsFile { private get; set; }
96
97 public string WixprojectFile { private get; set; }
98
99 /// <summary>
100 /// Gets the list of bindpaths.
101 /// </summary>
102 public List<BindPath> BindPaths { get; private set; }
103
104 /// <summary>
105 /// Gets the list of target bindpaths.
106 /// </summary>
107 public List<BindPath> TargetBindPaths { get; private set; }
108
109 /// <summary>
110 /// Gets the list of updated bindpaths.
111 /// </summary>
112 public List<BindPath> UpdatedBindPaths { get; private set; }
113
114 /// <summary>
115 /// Gets or sets the option to enable building binary delta patches.
116 /// </summary>
117 /// <value>The option to enable building binary delta patches.</value>
118 public bool DeltaBinaryPatch { get; set; }
119
120 /// <summary>
121 /// Gets or sets the cabinet cache location.
122 /// </summary>
123 public string CabCachePath { get; set; }
124
125 /// <summary>
126 /// Gets or sets the number of threads to use for cabinet creation.
127 /// </summary>
128 /// <value>The number of threads to use for cabinet creation.</value>
129 public int CabbingThreadCount { get; set; }
130
131 /// <summary>
132 /// Gets or sets the default compression level to use for cabinets
133 /// that don't have their compression level explicitly set.
134 /// </summary>
135 public CompressionLevel DefaultCompressionLevel { get; set; }
136
137 /// <summary>
138 /// Gets and sets the location to save the WixPdb.
139 /// </summary>
140 /// <value>The location in which to save the WixPdb. Null if the the WixPdb should not be output.</value>
141 public string PdbFile { get; set; }
142
143 public List<string> Ices { get; private set; }
144
145 public List<string> SuppressIces { get; private set; }
146
147 /// <summary>
148 /// Gets and sets the option to suppress resetting ACLs by the binder.
149 /// </summary>
150 /// <value>The option to suppress resetting ACLs by the binder.</value>
151 public bool SuppressAclReset { get; set; }
152
153 /// <summary>
154 /// Gets and sets the option to suppress creating an image for MSI/MSM.
155 /// </summary>
156 /// <value>The option to suppress creating an image for MSI/MSM.</value>
157 public bool SuppressLayout { get; set; }
158
159 /// <summary>
160 /// Gets and sets the option to suppress MSI/MSM validation.
161 /// </summary>
162 /// <value>The option to suppress MSI/MSM validation.</value>
163 /// <remarks>This must be set before calling Bind.</remarks>
164 public bool SuppressValidation { get; set; }
165
166 /// <summary>
167 /// Gets and sets the option to suppress adding _Validation table rows.
168 /// </summary>
169 public bool SuppressAddingValidationRows { get; set; }
170
171 /// <summary>
172 /// Gets or sets the localizer.
173 /// </summary>
174 /// <value>The localizer.</value>
175 public Localizer Localizer { get; set; }
176
177 /// <summary>
178 /// Gets or sets the temporary path for the Binder. If left null, the binder
179 /// will use %TEMP% environment variable.
180 /// </summary>
181 /// <value>Path to temp files.</value>
182 public string TempFilesLocation { get; set; }
183
184 /// <summary>
185 /// Gets or sets the Wix variable resolver.
186 /// </summary>
187 /// <value>The Wix variable resolver.</value>
188 public WixVariableResolver WixVariableResolver { get; set; }
189
190 /// <summary>
191 /// Add a binder extension.
192 /// </summary>
193 /// <param name="extension">New extension.</param>
194 public void AddExtension(IBinderExtension extension)
195 {
196 this.extensions.Add(extension);
197 }
198
199 /// <summary>
200 /// Add a file manager extension.
201 /// </summary>
202 /// <param name="extension">New file manager.</param>
203 public void AddExtension(IBinderFileManager extension)
204 {
205 this.fileManagers.Add(extension);
206 }
207
208 /// <summary>
209 /// Binds an output.
210 /// </summary>
211 /// <param name="output">The output to bind.</param>
212 /// <param name="file">The Windows Installer file to create.</param>
213 /// <remarks>The Binder.DeleteTempFiles method should be called after calling this method.</remarks>
214 /// <returns>true if binding completed successfully; false otherwise</returns>
215 public bool Bind(Output output, string file)
216 {
217 // Ensure the cabinet cache path exists if we are going to use it.
218 if (!String.IsNullOrEmpty(this.CabCachePath))
219 {
220 Directory.CreateDirectory(this.CabCachePath);
221 }
222
223 this.fileManagerCore = new BinderFileManagerCore();
224 this.fileManagerCore.CabCachePath = this.CabCachePath;
225 this.fileManagerCore.Output = output;
226 this.fileManagerCore.TempFilesLocation = this.TempFilesLocation;
227 this.fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal);
228 this.fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target);
229 this.fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated);
230 foreach (IBinderFileManager fileManager in this.fileManagers)
231 {
232 fileManager.Core = this.fileManagerCore;
233 }
234
235 this.core = new BinderCore();
236 this.core.FileManagerCore = this.fileManagerCore;
237
238 this.WriteBuildInfoTable(output, file);
239
240 // Initialize extensions.
241 foreach (IBinderExtension extension in this.extensions)
242 {
243 extension.Core = this.core;
244
245 extension.Initialize(output);
246 }
247
248 // Gather all the wix variables.
249 Table wixVariableTable = output.Tables["WixVariable"];
250 if (null != wixVariableTable)
251 {
252 foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows)
253 {
254 this.WixVariableResolver.AddVariable(wixVariableRow);
255 }
256 }
257
258 IEnumerable<FileTransfer> fileTransfers = null;
259 IEnumerable<string> contentPaths = null;
260
261 switch (output.Type)
262 {
263 case OutputType.Bundle:
264 this.BindBundle(output, file, out fileTransfers, out contentPaths);
265 break;
266
267 case OutputType.Transform:
268 this.BindTransform(output, file);
269 break;
270
271 default:
272 this.BindDatabase(output, file, out fileTransfers, out contentPaths);
273 break;
274 }
275
276
277 // Layout media
278 try
279 {
280 this.LayoutMedia(fileTransfers);
281 }
282 finally
283 {
284 if (!String.IsNullOrEmpty(this.ContentsFile) && contentPaths != null)
285 {
286 this.CreateContentsFile(this.ContentsFile, contentPaths);
287 }
288
289 if (!String.IsNullOrEmpty(this.OutputsFile) && fileTransfers != null)
290 {
291 this.CreateOutputsFile(this.OutputsFile, fileTransfers, this.PdbFile);
292 }
293
294 if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && fileTransfers != null)
295 {
296 this.CreateBuiltOutputsFile(this.BuiltOutputsFile, fileTransfers, this.PdbFile);
297 }
298 }
299
300 this.core = null;
301
302 return Messaging.Instance.EncounteredError;
303 }
304
305 /// <summary>
306 /// Does any housekeeping after Bind.
307 /// </summary>
308 /// <param name="tidy">Whether or not any actual tidying should be done.</param>
309 public void Cleanup(bool tidy)
310 {
311 if (tidy)
312 {
313 if (!this.DeleteTempFiles())
314 {
315 this.core.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation));
316 }
317 }
318 else
319 {
320 this.core.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation));
321 }
322 }
323
324 /// <summary>
325 /// Cleans up the temp files used by the Binder.
326 /// </summary>
327 /// <returns>True if all files were deleted, false otherwise.</returns>
328 private bool DeleteTempFiles()
329 {
330 bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.core);
331 return deleted;
332 }
333
334 /// <summary>
335 /// Populates the WixBuildInfo table in an output.
336 /// </summary>
337 /// <param name="output">The output.</param>
338 /// <param name="databaseFile">The output file if OutputFile not set.</param>
339 private void WriteBuildInfoTable(Output output, string outputFile)
340 {
341 Table buildInfoTable = output.EnsureTable(this.core.TableDefinitions["WixBuildInfo"]);
342 Row buildInfoRow = buildInfoTable.CreateRow(null);
343
344 Assembly executingAssembly = Assembly.GetExecutingAssembly();
345 FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location);
346 buildInfoRow[0] = fileVersion.FileVersion;
347 buildInfoRow[1] = outputFile;
348
349 if (!String.IsNullOrEmpty(this.WixprojectFile))
350 {
351 buildInfoRow[2] = this.WixprojectFile;
352 }
353
354 if (!String.IsNullOrEmpty(this.PdbFile))
355 {
356 buildInfoRow[3] = this.PdbFile;
357 }
358 }
359
360 /// <summary>
361 /// Binds a bundle.
362 /// </summary>
363 /// <param name="bundle">The bundle to bind.</param>
364 /// <param name="bundleFile">The bundle to create.</param>
365 private void BindBundle(Output bundle, string bundleFile, out IEnumerable<FileTransfer> fileTransfers, out IEnumerable<string> contentPaths)
366 {
367 BindBundleCommand command = new BindBundleCommand();
368 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
369 command.Extensions = this.extensions;
370 command.FileManagerCore = this.fileManagerCore;
371 command.FileManagers = this.fileManagers;
372 command.Output = bundle;
373 command.OutputPath = bundleFile;
374 command.PdbFile = this.PdbFile;
375 command.TableDefinitions = this.core.TableDefinitions;
376 command.TempFilesLocation = this.TempFilesLocation;
377 command.WixVariableResolver = this.WixVariableResolver;
378 command.Execute();
379
380 fileTransfers = command.FileTransfers;
381 contentPaths = command.ContentFilePaths;
382 }
383
384 /// <summary>
385 /// Binds a databse.
386 /// </summary>
387 /// <param name="output">The output to bind.</param>
388 /// <param name="databaseFile">The database file to create.</param>
389 private void BindDatabase(Output output, string databaseFile, out IEnumerable<FileTransfer> fileTransfers, out IEnumerable<string> contentPaths)
390 {
391 Validator validator = null;
392
393 // tell the binder about the validator if validation isn't suppressed
394 if (!this.SuppressValidation && (OutputType.Module == output.Type || OutputType.Product == output.Type))
395 {
396 validator = new Validator();
397 validator.TempFilesLocation = Path.Combine(this.TempFilesLocation, "validate");
398
399 // set the default cube file
400 string lightDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
401 string cubePath = (OutputType.Module == output.Type) ? Path.Combine(lightDirectory, "mergemod.cub") : Path.Combine(lightDirectory, "darice.cub");
402 validator.AddCubeFile(cubePath);
403
404 // by default, disable ICEs that have equivalent-or-better checks in WiX
405 this.SuppressIces.Add("ICE08");
406 this.SuppressIces.Add("ICE33");
407 this.SuppressIces.Add("ICE47");
408 this.SuppressIces.Add("ICE66");
409
410 // set the ICEs
411 validator.ICEs = this.Ices.ToArray();
412
413 // set the suppressed ICEs
414 validator.SuppressedICEs = this.SuppressIces.ToArray();
415 }
416
417 BindDatabaseCommand command = new BindDatabaseCommand();
418 command.CabbingThreadCount = this.CabbingThreadCount;
419 command.Codepage = this.Localizer == null ? -1 : this.Localizer.Codepage;
420 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
421 command.Extensions = this.extensions;
422 command.FileManagerCore = this.fileManagerCore;
423 command.FileManagers = this.fileManagers;
424 command.InspectorExtensions = this.inspectorExtensions;
425 command.Localizer = this.Localizer;
426 command.PdbFile = this.PdbFile;
427 command.Output = output;
428 command.OutputPath = databaseFile;
429 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
430 command.SuppressLayout = this.SuppressLayout;
431 command.TableDefinitions = this.core.TableDefinitions;
432 command.TempFilesLocation = this.TempFilesLocation;
433 command.Validator = validator;
434 command.WixVariableResolver = this.WixVariableResolver;
435 command.Execute();
436
437 fileTransfers = command.FileTransfers;
438 contentPaths = command.ContentFilePaths;
439 }
440
441 /// <summary>
442 /// Binds a transform.
443 /// </summary>
444 /// <param name="transform">The transform to bind.</param>
445 /// <param name="outputPath">The transform to create.</param>
446 private void BindTransform(Output transform, string outputPath)
447 {
448 BindTransformCommand command = new BindTransformCommand();
449 command.Extensions = this.extensions;
450 command.FileManagers = this.fileManagers;
451 command.TableDefinitions = this.core.TableDefinitions;
452 command.TempFilesLocation = this.TempFilesLocation;
453 command.Transform = transform;
454 command.OutputPath = outputPath;
455 command.Execute();
456 }
457
458 /// <summary>
459 /// Final step in binding that transfers (moves/copies) all files generated into the appropriate
460 /// location in the source image
461 /// </summary>
462 /// <param name="fileTransfers">List of files to transfer.</param>
463 private void LayoutMedia(IEnumerable<FileTransfer> transfers)
464 {
465 if (null != transfers && transfers.Any())
466 {
467 this.core.OnMessage(WixVerboses.LayingOutMedia());
468
469 TransferFilesCommand command = new TransferFilesCommand();
470 command.FileManagers = this.fileManagers;
471 command.FileTransfers = transfers;
472 command.SuppressAclReset = this.SuppressAclReset;
473 command.Execute();
474 }
475 }
476
477 /// <summary>
478 /// Get the source path of a directory.
479 /// </summary>
480 /// <param name="directories">All cached directories.</param>
481 /// <param name="componentIdGenSeeds">Hash table of Component GUID generation seeds indexed by directory id.</param>
482 /// <param name="directory">Directory identifier.</param>
483 /// <param name="canonicalize">Canonicalize the path for standard directories.</param>
484 /// <returns>Source path of a directory.</returns>
485 internal static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize)
486 {
487 if (!directories.Contains(directory))
488 {
489 throw new WixException(WixErrors.ExpectedDirectory(directory));
490 }
491
492 ResolvedDirectory resolvedDirectory = (ResolvedDirectory)directories[directory];
493
494 if (null == resolvedDirectory.Path)
495 {
496 if (null != componentIdGenSeeds && componentIdGenSeeds.Contains(directory))
497 {
498 resolvedDirectory.Path = (string)componentIdGenSeeds[directory];
499 }
500 else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory))
501 {
502 // when canonicalization is on, standard directories are treated equally
503 resolvedDirectory.Path = directory;
504 }
505 else
506 {
507 string name = resolvedDirectory.Name;
508
509 if (canonicalize && null != name)
510 {
511 name = name.ToLower(CultureInfo.InvariantCulture);
512 }
513
514 if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent))
515 {
516 resolvedDirectory.Path = name;
517 }
518 else
519 {
520 string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize);
521
522 if (null != resolvedDirectory.Name)
523 {
524 resolvedDirectory.Path = Path.Combine(parentPath, name);
525 }
526 else
527 {
528 resolvedDirectory.Path = parentPath;
529 }
530 }
531 }
532 }
533
534 return resolvedDirectory.Path;
535 }
536
537 /// <summary>
538 /// Gets the source path of a file.
539 /// </summary>
540 /// <param name="directories">All cached directories in <see cref="ResolvedDirectory"/>.</param>
541 /// <param name="directoryId">Parent directory identifier.</param>
542 /// <param name="fileName">File name (in long|source format).</param>
543 /// <param name="compressed">Specifies the package is compressed.</param>
544 /// <param name="useLongName">Specifies the package uses long file names.</param>
545 /// <returns>Source path of file relative to package directory.</returns>
546 internal static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName)
547 {
548 string fileSourcePath = Installer.GetName(fileName, true, useLongName);
549
550 if (compressed)
551 {
552 // Use just the file name of the file since all uncompressed files must appear
553 // in the root of the image in a compressed package.
554 }
555 else
556 {
557 // Get the relative path of where we want the file to be layed out as specified
558 // in the Directory table.
559 string directoryPath = Binder.GetDirectoryPath(directories, null, directoryId, false);
560 fileSourcePath = Path.Combine(directoryPath, fileSourcePath);
561 }
562
563 // Strip off "SourceDir" if it's still on there.
564 if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal))
565 {
566 fileSourcePath = fileSourcePath.Substring(10);
567 }
568
569 return fileSourcePath;
570 }
571
572 /// <summary>
573 /// Writes the paths to the content files included in the package to a text file.
574 /// </summary>
575 /// <param name="path">Path to write file.</param>
576 /// <param name="contentFilePaths">Collection of paths to content files that will be written to file.</param>
577 private void CreateContentsFile(string path, IEnumerable<string> contentFilePaths)
578 {
579 string directory = Path.GetDirectoryName(path);
580 if (!Directory.Exists(directory))
581 {
582 Directory.CreateDirectory(directory);
583 }
584
585 using (StreamWriter contents = new StreamWriter(path, false))
586 {
587 foreach (string contentPath in contentFilePaths)
588 {
589 contents.WriteLine(contentPath);
590 }
591 }
592 }
593
594 /// <summary>
595 /// Writes the paths to the content files included in the bundle to a text file.
596 /// </summary>
597 /// <param name="path">Path to write file.</param>
598 /// <param name="payloads">Collection of payloads whose source will be written to file.</param>
599 private void CreateContentsFile(string path, IEnumerable<WixBundlePayloadRow> payloads)
600 {
601 string directory = Path.GetDirectoryName(path);
602 if (!Directory.Exists(directory))
603 {
604 Directory.CreateDirectory(directory);
605 }
606
607 using (StreamWriter contents = new StreamWriter(path, false))
608 {
609 foreach (WixBundlePayloadRow payload in payloads)
610 {
611 if (payload.ContentFile)
612 {
613 contents.WriteLine(payload.FullFileName);
614 }
615 }
616 }
617 }
618
619 /// <summary>
620 /// Writes the paths to the output files to a text file.
621 /// </summary>
622 /// <param name="path">Path to write file.</param>
623 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
624 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
625 private void CreateOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
626 {
627 string directory = Path.GetDirectoryName(path);
628 if (!Directory.Exists(directory))
629 {
630 Directory.CreateDirectory(directory);
631 }
632
633 using (StreamWriter outputs = new StreamWriter(path, false))
634 {
635 foreach (FileTransfer fileTransfer in fileTransfers)
636 {
637 // Don't list files where the source is the same as the destination since
638 // that might be the only place the file exists. The outputs file is often
639 // used to delete stuff and losing the original source would be bad.
640 if (!fileTransfer.Redundant)
641 {
642 outputs.WriteLine(fileTransfer.Destination);
643 }
644 }
645
646 if (!String.IsNullOrEmpty(pdbPath))
647 {
648 outputs.WriteLine(Path.GetFullPath(pdbPath));
649 }
650 }
651 }
652
653 /// <summary>
654 /// Writes the paths to the built output files to a text file.
655 /// </summary>
656 /// <param name="path">Path to write file.</param>
657 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
658 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
659 private void CreateBuiltOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
660 {
661 string directory = Path.GetDirectoryName(path);
662 if (!Directory.Exists(directory))
663 {
664 Directory.CreateDirectory(directory);
665 }
666
667 using (StreamWriter outputs = new StreamWriter(path, false))
668 {
669 foreach (FileTransfer fileTransfer in fileTransfers)
670 {
671 // Only write the built file transfers. Also, skip redundant
672 // files for the same reason spelled out in this.CreateOutputsFile().
673 if (fileTransfer.Built && !fileTransfer.Redundant)
674 {
675 outputs.WriteLine(fileTransfer.Destination);
676 }
677 }
678
679 if (!String.IsNullOrEmpty(pdbPath))
680 {
681 outputs.WriteLine(Path.GetFullPath(pdbPath));
682 }
683 }
684 }
685 }
686}