// 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.WindowsInstaller.Bind { using System; using System.Collections.Generic; using System.IO; using System.Linq; using WixToolset.Core.Bind; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; /// /// Binds a databse. /// internal class BindDatabaseCommand { // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, Validator validator) : this(context, backendExtension, null, validator) { } public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, Validator validator) { this.ServiceProvider = context.ServiceProvider; this.Messaging = context.ServiceProvider.GetService(); this.BackendHelper = context.ServiceProvider.GetService(); this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); this.PathResolver = this.ServiceProvider.GetService(); this.CabbingThreadCount = context.CabbingThreadCount; this.CabCachePath = context.CabCachePath; this.Codepage = context.Codepage; this.DefaultCompressionLevel = context.DefaultCompressionLevel; this.DelayedFields = context.DelayedFields; this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions); this.Intermediate = context.IntermediateRepresentation; this.IntermediateFolder = context.IntermediateFolder; this.OutputPath = context.OutputPath; this.OutputPdbPath = context.PdbPath; this.PdbType = context.PdbType; this.SuppressLayout = context.SuppressLayout; this.SubStorages = subStorages; this.Validator = validator; this.BackendExtensions = backendExtension; } public IWixToolsetServiceProvider ServiceProvider { get; } private IMessaging Messaging { get; } private IBackendHelper BackendHelper { get; } private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } private IPathResolver PathResolver { get; } private int Codepage { get; } private int CabbingThreadCount { get; } private string CabCachePath { get; } private CompressionLevel? DefaultCompressionLevel { get; } public IEnumerable DelayedFields { get; } public IEnumerable ExpectedEmbeddedFiles { get; } public FileSystemManager FileSystemManager { get; } public bool DeltaBinaryPatch { get; set; } private IEnumerable BackendExtensions { get; } private IEnumerable SubStorages { get; } private Intermediate Intermediate { get; } private string OutputPath { get; } public PdbType PdbType { get; set; } private string OutputPdbPath { get; } private bool SuppressAddingValidationRows { get; } private bool SuppressLayout { get; } private string IntermediateFolder { get; } private Validator Validator { get; } public IBindResult Execute() { if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) && !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) { this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); } var section = this.Intermediate.Sections.Single(); var fileTransfers = new List(); var trackedFiles = new List(); var containsMergeModules = false; // If there are any fields to resolve later, create the cache to populate during bind. var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; // Load standard tables, authored custom tables, and extension custom tables. TableDefinitionCollection tableDefinitions; { var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions); command.Execute(); tableDefinitions = command.TableDefinitions; } // Process the summary information table before the other tables. bool compressed; bool longNames; int installerVersion; Platform platform; string modularizationSuffix; { var command = new BindSummaryInfoCommand(section); command.Execute(); compressed = command.Compressed; longNames = command.LongNames; installerVersion = command.InstallerVersion; platform = command.Platform; modularizationSuffix = command.ModularizationSuffix; } // Add binder variables for all properties. if (SectionType.Product == section.Type || variableCache != null) { foreach (var propertyRow in section.Symbols.OfType()) { // Set the ProductCode if it is to be generated. if ("ProductCode".Equals(propertyRow.Id.Id, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) { propertyRow.Value = Common.GenerateGuid(); #if TODO_PATCHING // Is this still necessary? // Update the target ProductCode in any instance transforms. foreach (SubStorage subStorage in this.Output.SubStorages) { Output subStorageOutput = subStorage.Data; if (OutputType.Transform != subStorageOutput.Type) { continue; } Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; foreach (Row row in instanceSummaryInformationTable.Rows) { if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) { row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); break; } } } #endif } // Add the property name and value to the variableCache. if (variableCache != null) { var key = String.Concat("property.", propertyRow.Id.Id); variableCache[key] = propertyRow.Value; } } } // Sequence all the actions. { var command = new SequenceActionsCommand(this.Messaging, section); command.Execute(); } { var command = new AddRequiredStandardDirectories(section, platform); command.Execute(); } { var command = new CreateSpecialPropertiesCommand(section); command.Execute(); } #if TODO_PATCHING ////if (OutputType.Patch == this.Output.Type) ////{ //// foreach (SubStorage substorage in this.Output.SubStorages) //// { //// Output transform = substorage.Data; //// ResolveFieldsCommand command = new ResolveFieldsCommand(); //// command.Tables = transform.Tables; //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; //// command.FileManagerCore = this.FileManagerCore; //// command.FileManagers = this.FileManagers; //// command.SupportDelayedResolution = false; //// command.TempFilesLocation = this.TempFilesLocation; //// command.WixVariableResolver = this.WixVariableResolver; //// command.Execute(); //// this.MergeUnrealTables(transform.Tables); //// } ////} #endif if (this.Messaging.EncounteredError) { return null; } this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). { var command = new ExtractEmbeddedFilesCommand(this.BackendHelper, this.ExpectedEmbeddedFiles); command.Execute(); trackedFiles.AddRange(command.TrackedFiles); } // This must occur after all variables and source paths have been resolved. List fileFacades; if (SectionType.Patch == section.Type) { var command = new GetFileFacadesFromTransforms(this.Messaging, this.FileSystemManager, this.SubStorages); command.Execute(); fileFacades = command.FileFacades; } else { var command = new GetFileFacadesCommand(section); command.Execute(); fileFacades = command.FileFacades; } // Retrieve file information from merge modules. if (SectionType.Product == section.Type) { var wixMergeSymbols = section.Symbols.OfType().ToList(); if (wixMergeSymbols.Any()) { containsMergeModules = true; var command = new ExtractMergeModuleFilesCommand(this.Messaging, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); command.Execute(); fileFacades.AddRange(command.MergeModulesFileFacades); } } // stop processing if an error previously occurred if (this.Messaging.EncounteredError) { return null; } // Gather information about files that do not come from merge modules. { var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); command.Execute(); } // stop processing if an error previously occurred if (this.Messaging.EncounteredError) { return null; } // Now that the variable cache is populated, resolve any delayed fields. if (this.DelayedFields.Any()) { var command = new ResolveDelayedFieldsCommand(this.Messaging, this.DelayedFields, variableCache); command.Execute(); } #if TODO_FINISH_UPDATE // use symbols instead of rows // Extended binder extensions can be called now that fields are resolved. { Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]); foreach (IBinderExtension extension in this.Extensions) { extension.AfterResolvedFields(this.Output); } List updatedFileFacades = new List(); foreach (Row updatedFile in updatedFiles.Rows) { string updatedId = updatedFile.FieldAsString(0); FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId)); updatedFileFacades.Add(updatedFacade); } if (updatedFileFacades.Any()) { UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updateFileFacades, variableCache, overwriteHash: false); //command.FileFacades = fileFacades; //command.UpdateFileFacades = updatedFileFacades; //command.ModularizationGuid = modularizationGuid; //command.Output = this.Output; //command.OverwriteHash = true; //command.TableDefinitions = this.TableDefinitions; //command.VariableCache = variableCache; command.Execute(); } } #endif // Set generated component guids. { var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section, platform); command.Execute(); } { var command = new ValidateComponentGuidsCommand(this.Messaging, section); command.Execute(); } // Add missing CreateFolder symbols to null-keypath components. { var command = new AddCreateFoldersCommand(section); command.Execute(); } // Update symbols that reference text files on disk. { var command = new UpdateFromTextFilesCommand(this.Messaging, section); command.Execute(); } // Assign files to media and update file sequences. Dictionary> filesByCabinetMedia; IEnumerable uncompressedFiles; { var order = new OptimizeFileFacadesOrderCommand(fileFacades); order.Execute(); fileFacades = order.FileFacades; var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); assign.Execute(); filesByCabinetMedia = assign.FileFacadesByCabinetMedia; uncompressedFiles = assign.UncompressedFileFacades; var update = new UpdateMediaSequencesCommand(section, fileFacades); update.Execute(); } // stop processing if an error previously occurred if (this.Messaging.EncounteredError) { return null; } // Time to create the output object. Try to put as much above here as possible, updating the IR is better. WindowsInstallerData output; { var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions, this.WindowsInstallerBackendHelper); command.Execute(); output = command.Output; } IEnumerable suppressedTableNames = null; if (output.Type == OutputType.Module) { // Modularize identifiers. var modularize = new ModularizeCommand(output, modularizationSuffix, section.Symbols.OfType()); modularize.Execute(); // Ensure all sequence tables in place because, mergemod.dll requires them. var unsuppress = new AddBackSuppressedSequenceTablesCommand(output, tableDefinitions); suppressedTableNames = unsuppress.Execute(); } else if (output.Type == OutputType.Patch) { foreach (var storage in this.SubStorages) { output.SubStorages.Add(storage); } } // Stop processing if an error previously occurred. if (this.Messaging.EncounteredError) { return null; } // Ensure the intermediate folder is created since delta patches will be // created there. Directory.CreateDirectory(this.IntermediateFolder); if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) { var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); command.Execute(); } // create cabinet files and process uncompressed files var layoutDirectory = Path.GetDirectoryName(this.OutputPath); if (!this.SuppressLayout || OutputType.Module == output.Type) { this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); var mediaTemplate = section.Symbols.OfType().FirstOrDefault(); var command = new CreateCabinetsCommand(this.ServiceProvider, this.BackendHelper, mediaTemplate); command.CabbingThreadCount = this.CabbingThreadCount; command.CabCachePath = this.CabCachePath; command.DefaultCompressionLevel = this.DefaultCompressionLevel; command.Output = output; command.Messaging = this.Messaging; command.BackendExtensions = this.BackendExtensions; command.LayoutDirectory = layoutDirectory; command.Compressed = compressed; command.ModularizationSuffix = modularizationSuffix; command.FileFacadesByCabinet = filesByCabinetMedia; command.ResolveMedia = this.ResolveMedia; command.TableDefinitions = tableDefinitions; command.IntermediateFolder = this.IntermediateFolder; command.Execute(); fileTransfers.AddRange(command.FileTransfers); trackedFiles.AddRange(command.TrackedFiles); } // stop processing if an error previously occurred if (this.Messaging.EncounteredError) { return null; } // We can create instance transforms since Component Guids and Outputs are created. if (output.Type == OutputType.Product) { var command = new CreateInstanceTransformsCommand(section, output, tableDefinitions, this.BackendHelper); command.Execute(); } else if (output.Type == OutputType.Patch) { // Copy output data back into the transforms. var command = new UpdateTransformsWithFileFacades(this.Messaging, output, this.SubStorages, tableDefinitions, fileFacades); command.Execute(); } // Generate database file. this.Messaging.Write(VerboseMessages.GeneratingDatabase()); { var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); trackedFiles.Add(trackMsi); var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, trackMsi.Path, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); command.Execute(); trackedFiles.AddRange(command.GeneratedTemporaryFiles); } // Stop processing if an error previously occurred. if (this.Messaging.EncounteredError) { return null; } // Merge modules. if (containsMergeModules) { this.Messaging.Write(VerboseMessages.MergingModules()); var command = new MergeModulesCommand(this.Messaging, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); command.Execute(); } if (this.Messaging.EncounteredError) { return null; } #if TODO_FINISH_VALIDATION // Validate the output if there is an MSI validator. if (null != this.Validator) { Stopwatch stopwatch = Stopwatch.StartNew(); // set the output file for source line information this.Validator.Output = this.Output; Messaging.Instance.Write(WixVerboses.ValidatingDatabase()); this.Validator.Validate(this.OutputPath); stopwatch.Stop(); Messaging.Instance.Write(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); // Stop processing if an error occurred. if (Messaging.Instance.EncounteredError) { return; } } #endif // Process uncompressed files. if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) { var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper, this.PathResolver); command.Compressed = compressed; command.FileFacades = uncompressedFiles; command.LayoutDirectory = layoutDirectory; command.LongNamesInImage = longNames; command.ResolveMedia = this.ResolveMedia; command.DatabasePath = this.OutputPath; command.Execute(); fileTransfers.AddRange(command.FileTransfers); trackedFiles.AddRange(command.TrackedFiles); } // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); var result = this.ServiceProvider.GetService(); result.FileTransfers = fileTransfers; result.TrackedFiles = trackedFiles; result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, output); return result; } private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, WindowsInstallerData output) { WixOutput wixout; if (String.IsNullOrEmpty(this.OutputPdbPath)) { wixout = WixOutput.Create(); } else { var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); trackedFiles.Add(trackPdb); wixout = WixOutput.Create(trackPdb.Path); } intermediate.Save(wixout); output.Save(wixout); wixout.Reopen(); return wixout; } private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory) { string layout = null; foreach (var extension in this.BackendExtensions) { layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory); if (!String.IsNullOrEmpty(layout)) { break; } } // If no binder file manager resolved the layout, do the default behavior. if (String.IsNullOrEmpty(layout)) { if (String.IsNullOrEmpty(mediaLayoutDirectory)) { layout = layoutDirectory; } else if (Path.IsPathRooted(mediaLayoutDirectory)) { layout = mediaLayoutDirectory; } else { layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); } } return layout; } } }