// 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.Core { 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.Core.Bind; using WixToolset.Data; using WixToolset.Data.Bind; using WixToolset.Data.Tuples; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; /// /// Binder of the WiX toolset. /// public sealed class Binder { //private BinderCore core; //private List extensions; //private List fileManagers; 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(); } private IBindContext Context { get; set; } //private TableDefinitionCollection TableDefinitions { get; } //public IEnumerable BackendFactories { get; set; } //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. internal 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); //} public bool Bind(IBindContext context) { this.Context = context; //if (!String.IsNullOrEmpty(this.Context.FileManagerCore.CabCachePath)) //{ // Directory.CreateDirectory(this.Context.FileManagerCore.CabCachePath); //} //this.core = new BinderCore(); //this.core.FileManagerCore = this.Context.FileManagerCore; this.WriteBuildInfoTable(this.Context.IntermediateRepresentation, this.Context.OutputPath); // Prebind. // this.Context.Extensions = this.Context.ExtensionManager.Create(); foreach (IBinderExtension extension in this.Context.Extensions) { extension.PreBind(this.Context); } // Resolve. // var resolveResult = this.Resolve(); this.Context.DelayedFields = resolveResult.DelayedFields; this.Context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; // Backend. // var bindResult = this.BackendBind(); if (bindResult != null) { // Postbind. // foreach (IBinderExtension extension in this.Context.Extensions) { extension.PostBind(bindResult); } // Layout. // this.Layout(bindResult); } return this.Context.Messaging.EncounteredError; } private ResolveResult Resolve() { var buildingPatch = this.Context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); IEnumerable delayedFields; { var command = new ResolveFieldsCommand(); command.Messaging = this.Context.Messaging; command.BuildingPatch = buildingPatch; command.BindVariableResolver = this.Context.WixVariableResolver; command.BindPaths = this.Context.BindPaths; command.Extensions = this.Context.Extensions; command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; command.IntermediateFolder = this.Context.IntermediateFolder; command.Intermediate = this.Context.IntermediateRepresentation; command.SupportDelayedResolution = true; command.Execute(); delayedFields = command.DelayedFields; } #if REVISIT_FOR_PATCHING if (this.Context.IntermediateRepresentation.SubStorages != null) { foreach (SubStorage transform in this.Context.IntermediateRepresentation.SubStorages) { var command = new ResolveFieldsCommand(); command.BuildingPatch = buildingPatch; command.BindVariableResolver = this.Context.WixVariableResolver; command.BindPaths = this.Context.BindPaths; command.Extensions = this.Context.Extensions; command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; command.IntermediateFolder = this.Context.IntermediateFolder; command.Intermediate = this.Context.IntermediateRepresentation; command.SupportDelayedResolution = false; command.Execute(); } } #endif var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); return new ResolveResult { ExpectedEmbeddedFiles = expectedEmbeddedFiles, DelayedFields = delayedFields, }; } private BindResult BackendBind() { var backendFactories = this.Context.ExtensionManager.Create(); var entrySection = this.Context.IntermediateRepresentation.Sections[0]; foreach (var factory in backendFactories) { if (factory.TryCreateBackend(entrySection.Type.ToString(), this.Context.OutputPath, null, out var backend)) { var result = backend.Bind(this.Context); return result; } } // TODO: messaging that a backend could not be found to bind the output type? return null; } private void Layout(BindResult result) { try { this.LayoutMedia(result.FileTransfers); } finally { if (!String.IsNullOrEmpty(this.Context.ContentsFile) && result.ContentFilePaths != null) { this.CreateContentsFile(this.Context.ContentsFile, result.ContentFilePaths); } if (!String.IsNullOrEmpty(this.Context.OutputsFile) && result.FileTransfers != null) { this.CreateOutputsFile(this.Context.OutputsFile, result.FileTransfers, this.Context.OutputPdbPath); } if (!String.IsNullOrEmpty(this.Context.BuiltOutputsFile) && result.FileTransfers != null) { this.CreateBuiltOutputsFile(this.Context.BuiltOutputsFile, result.FileTransfers, this.Context.OutputPdbPath); } } } /// /// 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 #if false 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); } //var fileManagerCore = new BinderFileManagerCore(); //fileManagerCore.CabCachePath = this.CabCachePath; //fileManagerCore.Output = output; //fileManagerCore.TempFilesLocation = this.TempFilesLocation; //fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal); //fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target); //fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated); //foreach (IBinderFileManager fileManager in this.fileManagers) //{ // fileManager.Core = fileManagerCore; //} this.core = new BinderCore(); this.core.FileManagerCore = 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); // } //} //BindContext context = new BindContext(); //context.CabbingThreadCount = this.CabbingThreadCount; //context.DefaultCompressionLevel = this.DefaultCompressionLevel; //context.Extensions = this.extensions; //context.FileManagerCore = fileManagerCore; //context.FileManagers = this.fileManagers; //context.Ices = this.Ices; //context.IntermediateFolder = this.TempFilesLocation; //context.IntermediateRepresentation = output; //context.Localizer = this.Localizer; //context.OutputPath = file; //context.OutputPdbPath = this.PdbFile; //context.SuppressIces = this.SuppressIces; //context.SuppressValidation = this.SuppressValidation; //context.WixVariableResolver = this.WixVariableResolver; BindResult result = null; foreach (var factory in this.BackendFactories) { if (factory.TryCreateBackend(output.Type.ToString(), file, null, out var backend)) { result = backend.Bind(context); break; } } if (result == null) { // TODO: messaging that a backend could not be found to bind the output type? return false; } // Layout media try { this.LayoutMedia(result.FileTransfers); } finally { if (!String.IsNullOrEmpty(this.ContentsFile) && result.ContentFilePaths != null) { this.CreateContentsFile(this.ContentsFile, result.ContentFilePaths); } if (!String.IsNullOrEmpty(this.OutputsFile) && result.FileTransfers != null) { this.CreateOutputsFile(this.OutputsFile, result.FileTransfers, this.PdbFile); } if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && result.FileTransfers != null) { this.CreateBuiltOutputsFile(this.BuiltOutputsFile, result.FileTransfers, this.PdbFile); } } this.core = null; return Messaging.Instance.EncounteredError; } #endif /// /// 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.Context.Messaging.Write(WarningMessages.FailedToDeleteTempDir(this.TempFilesLocation)); } } else { this.Context.Messaging.Write(VerboseMessages.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.Context.Messaging); return deleted; } /// /// Populates the WixBuildInfo table in an output. /// /// The output. /// The output file if OutputFile not set. private void WriteBuildInfoTable(Intermediate output, string outputFile) { var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment); Assembly executingAssembly = Assembly.GetExecutingAssembly(); FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); var buildInfoRow = new WixBuildInfoTuple(); buildInfoRow.WixVersion = fileVersion.FileVersion; buildInfoRow.WixOutputFile = outputFile; if (!String.IsNullOrEmpty(this.Context.WixprojectFile)) { buildInfoRow.WixProjectFile = this.Context.WixprojectFile; } if (!String.IsNullOrEmpty(this.Context.OutputPdbPath)) { buildInfoRow.WixPdbFile = this.Context.OutputPdbPath; } entrySection.Tuples.Add(buildInfoRow); } #if DELETE_THIS_CODE /// /// 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(); } #endif /// /// 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.Context.Messaging.Write(VerboseMessages.LayingOutMedia()); var command = new TransferFilesCommand(this.Context.Messaging, this.Context.BindPaths, this.Context.Extensions, transfers, this.Context.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. public static string GetDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, bool canonicalize) { if (!directories.ContainsKey(directory)) { throw new WixException(ErrorMessages.ExpectedDirectory(directory)); } ResolvedDirectory resolvedDirectory = (ResolvedDirectory)directories[directory]; if (null == resolvedDirectory.Path) { if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(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. public static string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) { string fileSourcePath = Common.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 (var payload in payloads) { if (payload.ContentFile) { var fullPath = Path.GetFullPath(payload.SourceFile); contents.WriteLine(fullPath); } } } } /// /// 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)); } } } } }