// 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. namespace WixToolset { using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using WixToolset.Bind; using WixToolset.Data; using WixToolset.Data.Rows; using WixToolset.Extensibility; using WixToolset.Msi; // TODO: (4.0) Refactor so that these don't need to be copied. // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs [Flags] internal enum WixFileSearchAttributes { Default = 0x001, MinVersionInclusive = 0x002, MaxVersionInclusive = 0x004, MinSizeInclusive = 0x008, MaxSizeInclusive = 0x010, MinDateInclusive = 0x020, MaxDateInclusive = 0x040, WantVersion = 0x080, WantExists = 0x100, IsDirectory = 0x200, } [Flags] internal enum WixRegistrySearchAttributes { Raw = 0x01, Compatible = 0x02, ExpandEnvironmentVariables = 0x04, WantValue = 0x08, WantExists = 0x10, Win64 = 0x20, } internal enum WixComponentSearchAttributes { KeyPath = 0x1, State = 0x2, WantDirectory = 0x4, } [Flags] internal enum WixProductSearchAttributes { Version = 0x1, Language = 0x2, State = 0x4, Assignment = 0x8, UpgradeCode = 0x10, } /// /// Binder of the WiX toolset. /// public sealed class Binder { private BinderCore core; private BinderFileManagerCore fileManagerCore; private List extensions; private List fileManagers; private List inspectorExtensions; public Binder() { this.DefaultCompressionLevel = CompressionLevel.High; this.BindPaths = new List(); this.TargetBindPaths = new List(); this.UpdatedBindPaths = new List(); this.extensions = new List(); this.fileManagers = new List(); this.inspectorExtensions = new List(); this.Ices = new List(); this.SuppressIces = new List(); } public string ContentsFile { private get; set; } public string OutputsFile { private get; set; } public string BuiltOutputsFile { private get; set; } public string WixprojectFile { private get; set; } /// /// Gets the list of bindpaths. /// public List BindPaths { get; private set; } /// /// Gets the list of target bindpaths. /// public List TargetBindPaths { get; private set; } /// /// Gets the list of updated bindpaths. /// public List UpdatedBindPaths { get; private set; } /// /// Gets or sets the option to enable building binary delta patches. /// /// The option to enable building binary delta patches. public bool DeltaBinaryPatch { get; set; } /// /// Gets or sets the cabinet cache location. /// public string CabCachePath { get; set; } /// /// Gets or sets the number of threads to use for cabinet creation. /// /// The number of threads to use for cabinet creation. public int CabbingThreadCount { get; set; } /// /// Gets or sets the default compression level to use for cabinets /// that don't have their compression level explicitly set. /// public CompressionLevel DefaultCompressionLevel { get; set; } /// /// Gets and sets the location to save the WixPdb. /// /// The location in which to save the WixPdb. Null if the the WixPdb should not be output. public string PdbFile { get; set; } public List Ices { get; private set; } public List SuppressIces { get; private set; } /// /// Gets and sets the option to suppress resetting ACLs by the binder. /// /// The option to suppress resetting ACLs by the binder. public bool SuppressAclReset { get; set; } /// /// Gets and sets the option to suppress creating an image for MSI/MSM. /// /// The option to suppress creating an image for MSI/MSM. public bool SuppressLayout { get; set; } /// /// Gets and sets the option to suppress MSI/MSM validation. /// /// The option to suppress MSI/MSM validation. /// This must be set before calling Bind. public bool SuppressValidation { get; set; } /// /// Gets and sets the option to suppress adding _Validation table rows. /// public bool SuppressAddingValidationRows { get; set; } /// /// Gets or sets the localizer. /// /// The localizer. public Localizer Localizer { get; set; } /// /// Gets or sets the temporary path for the Binder. If left null, the binder /// will use %TEMP% environment variable. /// /// Path to temp files. public string TempFilesLocation { get; set; } /// /// Gets or sets the Wix variable resolver. /// /// The Wix variable resolver. public WixVariableResolver WixVariableResolver { get; set; } /// /// Add a binder extension. /// /// New extension. public void AddExtension(IBinderExtension extension) { this.extensions.Add(extension); } /// /// Add a file manager extension. /// /// New file manager. public void AddExtension(IBinderFileManager extension) { this.fileManagers.Add(extension); } /// /// Binds an output. /// /// The output to bind. /// The Windows Installer file to create. /// The Binder.DeleteTempFiles method should be called after calling this method. /// true if binding completed successfully; false otherwise public bool Bind(Output output, string file) { // Ensure the cabinet cache path exists if we are going to use it. if (!String.IsNullOrEmpty(this.CabCachePath)) { Directory.CreateDirectory(this.CabCachePath); } this.fileManagerCore = new BinderFileManagerCore(); this.fileManagerCore.CabCachePath = this.CabCachePath; this.fileManagerCore.Output = output; this.fileManagerCore.TempFilesLocation = this.TempFilesLocation; this.fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal); this.fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target); this.fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated); foreach (IBinderFileManager fileManager in this.fileManagers) { fileManager.Core = this.fileManagerCore; } this.core = new BinderCore(); this.core.FileManagerCore = this.fileManagerCore; this.WriteBuildInfoTable(output, file); // Initialize extensions. foreach (IBinderExtension extension in this.extensions) { extension.Core = this.core; extension.Initialize(output); } // Gather all the wix variables. Table wixVariableTable = output.Tables["WixVariable"]; if (null != wixVariableTable) { foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) { this.WixVariableResolver.AddVariable(wixVariableRow); } } IEnumerable fileTransfers = null; IEnumerable contentPaths = null; switch (output.Type) { case OutputType.Bundle: this.BindBundle(output, file, out fileTransfers, out contentPaths); break; case OutputType.Transform: this.BindTransform(output, file); break; default: this.BindDatabase(output, file, out fileTransfers, out contentPaths); break; } // Layout media try { this.LayoutMedia(fileTransfers); } finally { if (!String.IsNullOrEmpty(this.ContentsFile) && contentPaths != null) { this.CreateContentsFile(this.ContentsFile, contentPaths); } if (!String.IsNullOrEmpty(this.OutputsFile) && fileTransfers != null) { this.CreateOutputsFile(this.OutputsFile, fileTransfers, this.PdbFile); } if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && fileTransfers != null) { this.CreateBuiltOutputsFile(this.BuiltOutputsFile, fileTransfers, this.PdbFile); } } this.core = null; return Messaging.Instance.EncounteredError; } /// /// Does any housekeeping after Bind. /// /// Whether or not any actual tidying should be done. public void Cleanup(bool tidy) { if (tidy) { if (!this.DeleteTempFiles()) { this.core.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation)); } } else { this.core.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation)); } } /// /// Cleans up the temp files used by the Binder. /// /// True if all files were deleted, false otherwise. private bool DeleteTempFiles() { bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.core); return deleted; } /// /// Populates the WixBuildInfo table in an output. /// /// The output. /// The output file if OutputFile not set. private void WriteBuildInfoTable(Output output, string outputFile) { Table buildInfoTable = output.EnsureTable(this.core.TableDefinitions["WixBuildInfo"]); Row buildInfoRow = buildInfoTable.CreateRow(null); Assembly executingAssembly = Assembly.GetExecutingAssembly(); FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); buildInfoRow[0] = fileVersion.FileVersion; buildInfoRow[1] = outputFile; if (!String.IsNullOrEmpty(this.WixprojectFile)) { buildInfoRow[2] = this.WixprojectFile; } if (!String.IsNullOrEmpty(this.PdbFile)) { buildInfoRow[3] = this.PdbFile; } } /// /// Binds a bundle. /// /// The bundle to bind. /// The bundle to create. private void BindBundle(Output bundle, string bundleFile, out IEnumerable fileTransfers, out IEnumerable contentPaths) { BindBundleCommand command = new BindBundleCommand(); command.DefaultCompressionLevel = this.DefaultCompressionLevel; command.Extensions = this.extensions; command.FileManagerCore = this.fileManagerCore; command.FileManagers = this.fileManagers; command.Output = bundle; command.OutputPath = bundleFile; command.PdbFile = this.PdbFile; command.TableDefinitions = this.core.TableDefinitions; command.TempFilesLocation = this.TempFilesLocation; command.WixVariableResolver = this.WixVariableResolver; command.Execute(); fileTransfers = command.FileTransfers; contentPaths = command.ContentFilePaths; } /// /// Binds a databse. /// /// The output to bind. /// The database file to create. private void BindDatabase(Output output, string databaseFile, out IEnumerable fileTransfers, out IEnumerable contentPaths) { Validator validator = null; // tell the binder about the validator if validation isn't suppressed if (!this.SuppressValidation && (OutputType.Module == output.Type || OutputType.Product == output.Type)) { validator = new Validator(); validator.TempFilesLocation = Path.Combine(this.TempFilesLocation, "validate"); // set the default cube file string lightDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string cubePath = (OutputType.Module == output.Type) ? Path.Combine(lightDirectory, "mergemod.cub") : Path.Combine(lightDirectory, "darice.cub"); validator.AddCubeFile(cubePath); // by default, disable ICEs that have equivalent-or-better checks in WiX this.SuppressIces.Add("ICE08"); this.SuppressIces.Add("ICE33"); this.SuppressIces.Add("ICE47"); this.SuppressIces.Add("ICE66"); // set the ICEs validator.ICEs = this.Ices.ToArray(); // set the suppressed ICEs validator.SuppressedICEs = this.SuppressIces.ToArray(); } BindDatabaseCommand command = new BindDatabaseCommand(); command.CabbingThreadCount = this.CabbingThreadCount; command.Codepage = this.Localizer == null ? -1 : this.Localizer.Codepage; command.DefaultCompressionLevel = this.DefaultCompressionLevel; command.Extensions = this.extensions; command.FileManagerCore = this.fileManagerCore; command.FileManagers = this.fileManagers; command.InspectorExtensions = this.inspectorExtensions; command.Localizer = this.Localizer; command.PdbFile = this.PdbFile; command.Output = output; command.OutputPath = databaseFile; command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; command.SuppressLayout = this.SuppressLayout; command.TableDefinitions = this.core.TableDefinitions; command.TempFilesLocation = this.TempFilesLocation; command.Validator = validator; command.WixVariableResolver = this.WixVariableResolver; command.Execute(); fileTransfers = command.FileTransfers; contentPaths = command.ContentFilePaths; } /// /// Binds a transform. /// /// The transform to bind. /// The transform to create. private void BindTransform(Output transform, string outputPath) { BindTransformCommand command = new BindTransformCommand(); command.Extensions = this.extensions; command.FileManagers = this.fileManagers; command.TableDefinitions = this.core.TableDefinitions; command.TempFilesLocation = this.TempFilesLocation; command.Transform = transform; command.OutputPath = outputPath; command.Execute(); } /// /// Final step in binding that transfers (moves/copies) all files generated into the appropriate /// location in the source image /// /// List of files to transfer. private void LayoutMedia(IEnumerable transfers) { if (null != transfers && transfers.Any()) { this.core.OnMessage(WixVerboses.LayingOutMedia()); TransferFilesCommand command = new TransferFilesCommand(); command.FileManagers = this.fileManagers; command.FileTransfers = transfers; command.SuppressAclReset = this.SuppressAclReset; command.Execute(); } } /// /// Get the source path of a directory. /// /// All cached directories. /// Hash table of Component GUID generation seeds indexed by directory id. /// Directory identifier. /// Canonicalize the path for standard directories. /// Source path of a directory. internal static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize) { if (!directories.Contains(directory)) { throw new WixException(WixErrors.ExpectedDirectory(directory)); } ResolvedDirectory resolvedDirectory = (ResolvedDirectory)directories[directory]; if (null == resolvedDirectory.Path) { if (null != componentIdGenSeeds && componentIdGenSeeds.Contains(directory)) { resolvedDirectory.Path = (string)componentIdGenSeeds[directory]; } else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory)) { // when canonicalization is on, standard directories are treated equally resolvedDirectory.Path = directory; } else { string name = resolvedDirectory.Name; if (canonicalize && null != name) { name = name.ToLower(CultureInfo.InvariantCulture); } if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) { resolvedDirectory.Path = name; } else { string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize); if (null != resolvedDirectory.Name) { resolvedDirectory.Path = Path.Combine(parentPath, name); } else { resolvedDirectory.Path = parentPath; } } } } return resolvedDirectory.Path; } /// /// Gets the source path of a file. /// /// All cached directories in . /// Parent directory identifier. /// File name (in long|source format). /// Specifies the package is compressed. /// Specifies the package uses long file names. /// Source path of file relative to package directory. internal static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName) { string fileSourcePath = Installer.GetName(fileName, true, useLongName); if (compressed) { // Use just the file name of the file since all uncompressed files must appear // in the root of the image in a compressed package. } else { // Get the relative path of where we want the file to be layed out as specified // in the Directory table. string directoryPath = Binder.GetDirectoryPath(directories, null, directoryId, false); fileSourcePath = Path.Combine(directoryPath, fileSourcePath); } // Strip off "SourceDir" if it's still on there. if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) { fileSourcePath = fileSourcePath.Substring(10); } return fileSourcePath; } /// /// Writes the paths to the content files included in the package to a text file. /// /// Path to write file. /// Collection of paths to content files that will be written to file. private void CreateContentsFile(string path, IEnumerable contentFilePaths) { string directory = Path.GetDirectoryName(path); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } using (StreamWriter contents = new StreamWriter(path, false)) { foreach (string contentPath in contentFilePaths) { contents.WriteLine(contentPath); } } } /// /// Writes the paths to the content files included in the bundle to a text file. /// /// Path to write file. /// Collection of payloads whose source will be written to file. private void CreateContentsFile(string path, IEnumerable payloads) { string directory = Path.GetDirectoryName(path); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } using (StreamWriter contents = new StreamWriter(path, false)) { foreach (WixBundlePayloadRow payload in payloads) { if (payload.ContentFile) { contents.WriteLine(payload.FullFileName); } } } } /// /// Writes the paths to the output files to a text file. /// /// Path to write file. /// Collection of files that were transferred to the output directory. /// Optional path to created .wixpdb. private void CreateOutputsFile(string path, IEnumerable fileTransfers, string pdbPath) { string directory = Path.GetDirectoryName(path); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } using (StreamWriter outputs = new StreamWriter(path, false)) { foreach (FileTransfer fileTransfer in fileTransfers) { // Don't list files where the source is the same as the destination since // that might be the only place the file exists. The outputs file is often // used to delete stuff and losing the original source would be bad. if (!fileTransfer.Redundant) { outputs.WriteLine(fileTransfer.Destination); } } if (!String.IsNullOrEmpty(pdbPath)) { outputs.WriteLine(Path.GetFullPath(pdbPath)); } } } /// /// Writes the paths to the built output files to a text file. /// /// Path to write file. /// Collection of files that were transferred to the output directory. /// Optional path to created .wixpdb. private void CreateBuiltOutputsFile(string path, IEnumerable fileTransfers, string pdbPath) { string directory = Path.GetDirectoryName(path); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } using (StreamWriter outputs = new StreamWriter(path, false)) { foreach (FileTransfer fileTransfer in fileTransfers) { // Only write the built file transfers. Also, skip redundant // files for the same reason spelled out in this.CreateOutputsFile(). if (fileTransfer.Built && !fileTransfer.Redundant) { outputs.WriteLine(fileTransfer.Destination); } } if (!String.IsNullOrEmpty(pdbPath)) { outputs.WriteLine(Path.GetFullPath(pdbPath)); } } } } }