// 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.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, string cubeFile) : this(context, backendExtension, null, cubeFile)
{
}
public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile)
{
this.ServiceProvider = context.ServiceProvider;
this.Messaging = context.ServiceProvider.GetService();
this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService();
this.PathResolver = this.ServiceProvider.GetService();
this.CabbingThreadCount = context.CabbingThreadCount;
this.CabCachePath = context.CabCachePath;
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.ResolvedCodepage = context.ResolvedCodepage;
this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage;
this.ResolvedLcid = context.ResolvedLcid;
this.SuppressLayout = context.SuppressLayout;
this.SubStorages = subStorages;
this.SuppressValidation = context.SuppressValidation;
this.Ices = context.Ices;
this.SuppressedIces = context.SuppressIces;
this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile };
this.BackendExtensions = backendExtension;
}
public IServiceProvider ServiceProvider { get; }
private IMessaging Messaging { get; }
private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; }
private IPathResolver PathResolver { 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 int? ResolvedCodepage { get; }
private int? ResolvedSummaryInformationCodepage { get; }
private int? ResolvedLcid { get; }
private bool SuppressAddingValidationRows { get; }
private bool SuppressLayout { get; }
private string IntermediateFolder { get; }
private bool SuppressValidation { get; }
private IEnumerable Ices { get; }
private IEnumerable SuppressedIces { get; }
private IEnumerable CubeFiles { 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 packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol(section) : null;
var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol(section) : null;
var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol(section) : null;
var fileTransfers = new List();
var trackedFiles = new List();
var containsMergeModules = false;
// 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;
}
// Calculate codepage
var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol);
// Process properties and create the delayed variable cache if needed.
Dictionary variableCache = null;
string productLanguage = null;
{
var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper);
command.Execute();
variableCache = command.DelayedVariablesCache;
productLanguage = command.ProductLanguage;
}
// Process the summary information table after properties are processed.
bool compressed;
bool longNames;
int installerVersion;
Platform platform;
string modularizationSuffix;
{
var branding = this.ServiceProvider.GetService();
var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding);
command.Execute();
compressed = command.Compressed;
longNames = command.LongNames;
installerVersion = command.InstallerVersion;
platform = command.Platform;
modularizationSuffix = command.ModularizationSuffix;
}
// Sequence all the actions.
{
var command = new SequenceActionsCommand(this.Messaging, section);
command.Execute();
}
if (section.Type == SectionType.Product || section.Type == SectionType.Module)
{
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 extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles);
trackedFiles.AddRange(extractedFiles);
}
// 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.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages);
command.Execute();
fileFacades = command.FileFacades;
}
else
{
var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper);
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, this.WindowsInstallerBackendHelper, 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;
}
// Process SoftwareTags in MSI packages.
if (SectionType.Product == section.Type)
{
var softwareTags = section.Symbols.OfType().ToList();
if (softwareTags.Any())
{
var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder);
command.Execute();
}
}
// 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())
{
this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache);
}
// Update symbols that reference text files on disk.
{
var command = new UpdateFromTextFilesCommand(this.Messaging, section);
command.Execute();
}
// Add missing CreateFolder symbols to null-keypath components.
{
var command = new AddCreateFoldersCommand(section);
command.Execute();
}
// Process dependency references.
if (SectionType.Product == section.Type || SectionType.Module == section.Type)
{
var dependencyRefs = section.Symbols.OfType().ToList();
if (dependencyRefs.Any())
{
var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs);
command.Execute();
}
}
// If there are any backend extensions, give them the opportunity to process
// the section now that the fields have all be resolved.
//
if (this.BackendExtensions.Any())
{
using (new IntermediateFieldContext("wix.bind.finalize"))
{
foreach (var extension in this.BackendExtensions)
{
extension.SymbolsFinalized(section);
}
var reresolvedFiles = section.Symbols
.OfType()
.Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize"))
.ToList();
if (reresolvedFiles.Any())
{
var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id));
var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false);
command.Execute();
}
}
if (this.Messaging.EncounteredError)
{
return null;
}
}
// Set generated component guids.
{
var command = new CalculateComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform);
command.Execute();
}
{
var command = new ValidateComponentGuidsCommand(this.Messaging, section);
command.Execute();
}
// Assign files to media and update file sequences.
Dictionary> filesByCabinetMedia;
IEnumerable uncompressedFiles;
{
var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, 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 WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better.
WindowsInstallerData data;
{
var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper);
data = command.Execute();
}
IEnumerable suppressedTableNames = null;
if (data.Type == OutputType.Module)
{
// Modularize identifiers.
var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType());
modularize.Execute();
// Ensure all sequence tables in place because, mergemod.dll requires them.
var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions);
suppressedTableNames = unsuppress.Execute();
}
else if (data.Type == OutputType.Patch)
{
foreach (var storage in this.SubStorages)
{
data.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 == data.Type)
{
this.Messaging.Write(VerboseMessages.CreatingCabinetFiles());
var mediaTemplate = section.Symbols.OfType().FirstOrDefault();
var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate);
command.CabbingThreadCount = this.CabbingThreadCount;
command.CabCachePath = this.CabCachePath;
command.DefaultCompressionLevel = this.DefaultCompressionLevel;
command.Data = data;
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 (data.Type == OutputType.Product)
{
var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper);
command.Execute();
}
else if (data.Type == OutputType.Patch)
{
// Copy output data back into the transforms.
var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades);
command.Execute();
}
// Generate database file.
{
this.Messaging.Write(VerboseMessages.GeneratingDatabase());
var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final);
trackedFiles.Add(trackMsi);
var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, 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;
}
// Validate the output if there are CUBe files and we're not explicitly suppressing validation.
if (this.CubeFiles != null && !this.SuppressValidation)
{
var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces);
command.Execute();
trackedFiles.AddRange(command.TrackedFiles);
}
if (this.Messaging.EncounteredError)
{
return null;
}
// Process uncompressed files.
if (!this.SuppressLayout && uncompressedFiles.Any())
{
var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, 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.WindowsInstallerBackendHelper.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, data);
return result;
}
private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol)
{
var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage;
if (String.IsNullOrEmpty(codepage))
{
codepage = this.ResolvedCodepage?.ToString() ?? "65001";
if (packageSymbol != null)
{
packageSymbol.Codepage = codepage;
}
else if (moduleSymbol != null)
{
moduleSymbol.Codepage = codepage;
}
else if (patchSymbol != null)
{
patchSymbol.Codepage = codepage;
}
}
return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage);
}
private T GetSingleSymbol(IntermediateSection section) where T : IntermediateSymbol
{
var symbols = section.Symbols.OfType().ToList();
if (1 != symbols.Count)
{
throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T)));
}
return symbols[0];
}
private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, WindowsInstallerData data)
{
WixOutput wixout;
if (String.IsNullOrEmpty(this.OutputPdbPath))
{
wixout = WixOutput.Create();
}
else
{
var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final);
trackedFiles.Add(trackPdb);
wixout = WixOutput.Create(trackPdb.Path);
}
intermediate.Save(wixout);
data.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;
}
}
}