From dbde9e7104b907bbbaea17e21247d8cafc8b3a4c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 14 Oct 2017 16:12:07 -0700 Subject: Massive refactoring to introduce the concept of IBackend --- .../Bind/BindDatabaseCommand.cs | 1282 ++++++++++++++++++++ 1 file changed, 1282 insertions(+) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..2e2c5417 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -0,0 +1,1282 @@ +// 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; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Bind; + using WixToolset.Core.Bind; + using WixToolset.Core.WindowsInstaller.Databases; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + using WixToolset.Msi; + + /// + /// Binds a databse. + /// + internal class BindDatabaseCommand + { + // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. + private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); + + public BindDatabaseCommand(IBindContext context, Validator validator) + { + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + + this.BindPaths = context.BindPaths; + 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.Extensions = context.Extensions; + this.Output = context.IntermediateRepresentation; + this.OutputPath = context.OutputPath; + this.PdbFile = context.OutputPdbPath; + this.IntermediateFolder = context.IntermediateFolder; + this.Validator = validator; + this.WixVariableResolver = context.WixVariableResolver; + + this.BackendExtensions = context.ExtensionManager.Create(); + } + + private IEnumerable BindPaths { 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 bool DeltaBinaryPatch { get; set; } + + private IEnumerable BackendExtensions { get; } + + private IEnumerable Extensions { get; } + + private IEnumerable InspectorExtensions { get; } + + private string PdbFile { get; } + + private Output Output { get; } + + private string OutputPath { get; } + + private bool SuppressAddingValidationRows { get; } + + private bool SuppressLayout { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string IntermediateFolder { get; } + + private Validator Validator { get; } + + private IBindVariableResolver WixVariableResolver { get; } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable ContentFilePaths { get; private set; } + + public void Execute() + { + List fileTransfers = new List(); + + HashSet suppressedTableNames = new HashSet(); + + // If there are any fields to resolve later, create the cache to populate during bind. + IDictionary variableCache = null; + if (this.DelayedFields.Any()) + { + variableCache = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + } + + this.LocalizeUI(this.Output.Tables); + + // Process the summary information table before the other tables. + bool compressed; + bool longNames; + int installerVersion; + string modularizationGuid; + { + BindSummaryInfoCommand command = new BindSummaryInfoCommand(); + command.Output = this.Output; + command.Execute(); + + compressed = command.Compressed; + longNames = command.LongNames; + installerVersion = command.InstallerVersion; + modularizationGuid = command.ModularizationGuid; + } + + // Stop processing if an error previously occurred. + if (Messaging.Instance.EncounteredError) + { + return; + } + + // Modularize identifiers and add tables with real streams to the import tables. + if (OutputType.Module == this.Output.Type) + { + // Gather all the suppress modularization identifiers + HashSet suppressModularizationIdentifiers = null; + Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"]; + if (null != wixSuppressModularizationTable) + { + suppressModularizationIdentifiers = new HashSet(wixSuppressModularizationTable.Rows.Select(row => (string)row[0])); + } + + foreach (Table table in this.Output.Tables) + { + table.Modularize(modularizationGuid, suppressModularizationIdentifiers); + } + } + + // This must occur after all variables and source paths have been resolved and after modularization. + List fileFacades; + { + GetFileFacadesCommand command = new GetFileFacadesCommand(); + command.FileTable = this.Output.Tables["File"]; + command.WixFileTable = this.Output.Tables["WixFile"]; + command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"]; + command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"]; + command.Execute(); + + fileFacades = command.FileFacades; + } + + ////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); + //// } + ////} + + { + CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand(); + command.PropertyTable = this.Output.Tables["Property"]; + command.WixPropertyTable = this.Output.Tables["WixProperty"]; + command.Execute(); + } + + if (Messaging.Instance.EncounteredError) + { + return; + } + + // Add binder variables for all properties. + Table propertyTable = this.Output.Tables["Property"]; + if (null != propertyTable) + { + foreach (PropertyRow propertyRow in propertyTable.Rows) + { + // Set the ProductCode if it is to be generated. + if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) + { + propertyRow.Value = Common.GenerateGuid(); + + // 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; + } + } + } + } + + // Add the property name and value to the variableCache. + if (null != variableCache) + { + string key = String.Concat("property.", Common.Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property)); + variableCache[key] = propertyRow.Value; + } + } + } + + // Extract files that come from cabinet files (this does not extract files from merge modules). + { + ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand(); + command.FilesWithEmbeddedFiles = this.ExpectedEmbeddedFiles; + command.Execute(); + } + + if (OutputType.Product == this.Output.Type) + { + // Retrieve files and their information from merge modules. + Table wixMergeTable = this.Output.Tables["WixMerge"]; + + if (null != wixMergeTable) + { + ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand(); + command.FileFacades = fileFacades; + command.FileTable = this.Output.Tables["File"]; + command.WixFileTable = this.Output.Tables["WixFile"]; + command.WixMergeTable = wixMergeTable; + command.OutputInstallerVersion = installerVersion; + command.SuppressLayout = this.SuppressLayout; + command.TempFilesLocation = this.IntermediateFolder; + command.Execute(); + + fileFacades.AddRange(command.MergeModulesFileFacades); + } + } + else if (OutputType.Patch == this.Output.Type) + { + // Merge transform data into the output object. + IEnumerable filesFromTransform = this.CopyFromTransformData(this.Output); + + fileFacades.AddRange(filesFromTransform); + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation()); + + // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table). + { + UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); + command.FileFacades = fileFacades; + command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); + command.ModularizationGuid = modularizationGuid; + command.Output = this.Output; + command.OverwriteHash = true; + command.TableDefinitions = this.TableDefinitions; + command.VariableCache = variableCache; + command.Execute(); + } + + // Set generated component guids. + this.SetComponentGuids(this.Output); + + // With the Component Guids set now we can create instance transforms. + this.CreateInstanceTransforms(this.Output); + + this.ValidateComponentGuids(this.Output); + + this.UpdateControlText(this.Output); + + if (this.DelayedFields.Any()) + { + ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand(); + command.OutputType = this.Output.Type; + command.DelayedFields = this.DelayedFields; + command.ModularizationGuid = null; + command.VariableCache = variableCache; + command.Execute(); + } + + // Assign files to media. + RowDictionary assignedMediaRows; + Dictionary> filesByCabinetMedia; + IEnumerable uncompressedFiles; + { + AssignMediaCommand command = new AssignMediaCommand(); + command.FilesCompressed = compressed; + command.FileFacades = fileFacades; + command.Output = this.Output; + command.TableDefinitions = this.TableDefinitions; + command.Execute(); + + assignedMediaRows = command.MediaRows; + filesByCabinetMedia = command.FileFacadesByCabinetMedia; + uncompressedFiles = command.UncompressedFileFacades; + } + + // Update file sequence. + this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows); + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + // 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(); + 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(); + } + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + Directory.CreateDirectory(this.IntermediateFolder); + + if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch) + { + CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand(); + command.FileFacades = fileFacades; + command.WixPatchIdTable = this.Output.Tables["WixPatchId"]; + command.TempFilesLocation = this.IntermediateFolder; + command.Execute(); + } + + // create cabinet files and process uncompressed files + string layoutDirectory = Path.GetDirectoryName(this.OutputPath); + if (!this.SuppressLayout || OutputType.Module == this.Output.Type) + { + Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles()); + + var command = new CreateCabinetsCommand(); + command.CabbingThreadCount = this.CabbingThreadCount; + command.CabCachePath = this.CabCachePath; + command.DefaultCompressionLevel = this.DefaultCompressionLevel; + command.Output = this.Output; + command.BackendExtensions = this.BackendExtensions; + command.LayoutDirectory = layoutDirectory; + command.Compressed = compressed; + command.FileRowsByCabinet = filesByCabinetMedia; + command.ResolveMedia = this.ResolveMedia; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.WixMediaTable = this.Output.Tables["WixMedia"]; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + } + + if (OutputType.Patch == this.Output.Type) + { + // copy output data back into the transforms + this.CopyToTransformData(this.Output); + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + // add back suppressed tables which must be present prior to merging in modules + if (OutputType.Product == this.Output.Type) + { + Table wixMergeTable = this.Output.Tables["WixMerge"]; + + if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) + { + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) + { + string sequenceTableName = sequence.ToString(); + Table sequenceTable = this.Output.Tables[sequenceTableName]; + + if (null == sequenceTable) + { + sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); + } + + if (0 == sequenceTable.Rows.Count) + { + suppressedTableNames.Add(sequenceTableName); + } + } + } + } + + //foreach (BinderExtension extension in this.Extensions) + //{ + // extension.PostBind(this.Context); + //} + + // generate database file + Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase()); + string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); + this.GenerateDatabase(this.Output, tempDatabaseFile, false, false); + + FileTransfer transfer; + if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future + { + transfer.Built = true; + fileTransfers.Add(transfer); + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + // Output the output to a file + Pdb pdb = new Pdb(); + pdb.Output = this.Output; + if (!String.IsNullOrEmpty(this.PdbFile)) + { + pdb.Save(this.PdbFile); + } + + // Merge modules. + if (OutputType.Product == this.Output.Type) + { + Messaging.Instance.OnMessage(WixVerboses.MergingModules()); + + MergeModulesCommand command = new MergeModulesCommand(); + command.FileFacades = fileFacades; + command.Output = this.Output; + command.OutputPath = tempDatabaseFile; + command.SuppressedTableNames = suppressedTableNames; + command.Execute(); + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + } + + // inspect the MSI prior to running ICEs + //InspectorCore inspectorCore = new InspectorCore(); + //foreach (InspectorExtension inspectorExtension in this.InspectorExtensions) + //{ + // inspectorExtension.Core = inspectorCore; + // inspectorExtension.InspectDatabase(tempDatabaseFile, pdb); + + // inspectorExtension.Core = null; // reset. + //} + + if (Messaging.Instance.EncounteredError) + { + return; + } + + // 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.OnMessage(WixVerboses.ValidatingDatabase()); + + this.Validator.Validate(tempDatabaseFile); + + stopwatch.Stop(); + Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + + // Stop processing if an error occurred. + if (Messaging.Instance.EncounteredError) + { + return; + } + } + + // Process uncompressed files. + if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) + { + var command = new ProcessUncompressedFilesCommand(); + command.Compressed = compressed; + command.FileFacades = uncompressedFiles; + command.LayoutDirectory = layoutDirectory; + command.LongNamesInImage = longNames; + command.MediaRows = assignedMediaRows; + command.ResolveMedia = this.ResolveMedia; + command.DatabasePath = tempDatabaseFile; + command.WixMediaTable = this.Output.Tables["WixMedia"]; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + } + + this.FileTransfers = fileTransfers; + this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList(); + } + + /// + /// Localize dialogs and controls. + /// + /// The tables to localize. + private void LocalizeUI(TableIndexedCollection tables) + { + Table dialogTable = tables["Dialog"]; + if (null != dialogTable) + { + foreach (Row row in dialogTable.Rows) + { + string dialog = (string)row[0]; + + if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + row[1] = localizedControl.X; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + row[2] = localizedControl.Y; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + row[3] = localizedControl.Width; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + row[4] = localizedControl.Height; + } + + row[5] = (int)row[5] | localizedControl.Attributes; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + row[6] = localizedControl.Text; + } + } + } + } + + Table controlTable = tables["Control"]; + if (null != controlTable) + { + foreach (Row row in controlTable.Rows) + { + string dialog = (string)row[0]; + string control = (string)row[1]; + + if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + row[3] = localizedControl.X.ToString(); + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + row[4] = localizedControl.Y.ToString(); + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + row[5] = localizedControl.Width.ToString(); + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + row[6] = localizedControl.Height.ToString(); + } + + row[7] = (int)row[7] | localizedControl.Attributes; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + row[9] = localizedControl.Text; + } + } + } + } + } + + /// + /// Copy file data between transform substorages and the patch output object + /// + /// The output to bind. + /// True if copying from transform to patch, false the other way. + private IEnumerable CopyFromTransformData(Output output) + { + var command = new CopyTransformDataCommand(); + command.CopyOutFileRows = true; + command.Output = output; + command.TableDefinitions = this.TableDefinitions; + command.Execute(); + + return command.FileFacades; + } + + /// + /// Copy file data between transform substorages and the patch output object + /// + /// The output to bind. + /// True if copying from transform to patch, false the other way. + private void CopyToTransformData(Output output) + { + var command = new CopyTransformDataCommand(); + command.CopyOutFileRows = false; + command.Output = output; + command.TableDefinitions = this.TableDefinitions; + command.Execute(); + } + + private void UpdateMediaSequences(OutputType outputType, IEnumerable fileFacades, RowDictionary mediaRows) + { + // Calculate sequence numbers and media disk id layout for all file media information objects. + if (OutputType.Module == outputType) + { + int lastSequence = 0; + foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. + { + facade.File.Sequence = ++lastSequence; + } + } + else + { + int lastSequence = 0; + MediaRow mediaRow = null; + Dictionary> patchGroups = new Dictionary>(); + + // sequence the non-patch-added files + foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. + { + if (null == mediaRow) + { + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + if (OutputType.Patch == outputType) + { + // patch Media cannot start at zero + lastSequence = mediaRow.LastSequence; + } + } + else if (mediaRow.DiskId != facade.WixFile.DiskId) + { + mediaRow.LastSequence = lastSequence; + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + } + + if (0 < facade.WixFile.PatchGroup) + { + List patchGroup = patchGroups[facade.WixFile.PatchGroup]; + + if (null == patchGroup) + { + patchGroup = new List(); + patchGroups.Add(facade.WixFile.PatchGroup, patchGroup); + } + + patchGroup.Add(facade); + } + else + { + facade.File.Sequence = ++lastSequence; + } + } + + if (null != mediaRow) + { + mediaRow.LastSequence = lastSequence; + mediaRow = null; + } + + // sequence the patch-added files + foreach (List patchGroup in patchGroups.Values) + { + foreach (FileFacade facade in patchGroup) + { + if (null == mediaRow) + { + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + } + else if (mediaRow.DiskId != facade.WixFile.DiskId) + { + mediaRow.LastSequence = lastSequence; + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + } + + facade.File.Sequence = ++lastSequence; + } + } + + if (null != mediaRow) + { + mediaRow.LastSequence = lastSequence; + } + } + } + + /// + /// Set the guids for components with generatable guids. + /// + /// Internal representation of the database to operate on. + private void SetComponentGuids(Output output) + { + Table componentTable = output.Tables["Component"]; + if (null != componentTable) + { + Hashtable registryKeyRows = null; + Hashtable directories = null; + Hashtable componentIdGenSeeds = null; + Dictionary> fileRows = null; + + // find components with generatable guids + foreach (ComponentRow componentRow in componentTable.Rows) + { + // component guid will be generated + if ("*" == componentRow.Guid) + { + if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath) + { + Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers)); + } + else if (componentRow.IsRegistryKeyPath) + { + if (null == registryKeyRows) + { + Table registryTable = output.Tables["Registry"]; + + registryKeyRows = new Hashtable(registryTable.Rows.Count); + + foreach (Row registryRow in registryTable.Rows) + { + registryKeyRows.Add((string)registryRow[0], registryRow); + } + } + + Row foundRow = registryKeyRows[componentRow.KeyPath] as Row; + + string bitness = componentRow.Is64Bit ? "64" : String.Empty; + if (null != foundRow) + { + string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]); + componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant(); + } + } + else // must be a File KeyPath + { + // if the directory table hasn't been loaded into an indexed hash + // of directory ids to target names do that now. + if (null == directories) + { + Table directoryTable = output.Tables["Directory"]; + + int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0; + + directories = new Hashtable(numDirectoryTableRows); + + // get the target paths for all directories + if (null != directoryTable) + { + foreach (Row row in directoryTable.Rows) + { + // if the directory Id already exists, we will skip it here since + // checking for duplicate primary keys is done later when importing tables + // into database + if (directories.ContainsKey(row[0])) + { + continue; + } + + string targetName = Common.GetName((string)row[2], false, true); + directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName)); + } + } + } + + // if the component id generation seeds have not been indexed + // from the WixDirectory table do that now. + if (null == componentIdGenSeeds) + { + Table wixDirectoryTable = output.Tables["WixDirectory"]; + + int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0; + + componentIdGenSeeds = new Hashtable(numWixDirectoryRows); + + // if there are any WixDirectory rows, build up the Component Guid + // generation seeds indexed by Directory/@Id. + if (null != wixDirectoryTable) + { + foreach (Row row in wixDirectoryTable.Rows) + { + componentIdGenSeeds.Add(row[0], (string)row[1]); + } + } + } + + // if the file rows have not been indexed by File.Component yet + // then do that now + if (null == fileRows) + { + Table fileTable = output.Tables["File"]; + + int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0; + + fileRows = new Dictionary>(numFileRows); + + if (null != fileTable) + { + foreach (FileRow file in fileTable.Rows) + { + List files; + if (!fileRows.TryGetValue(file.Component, out files)) + { + files = new List(); + fileRows.Add(file.Component, files); + } + + files.Add(file); + } + } + } + + // validate component meets all the conditions to have a generated guid + List currentComponentFiles = fileRows[componentRow.Component]; + int numFilesInComponent = currentComponentFiles.Count; + string path = null; + + foreach (FileRow fileRow in currentComponentFiles) + { + if (fileRow.File == componentRow.KeyPath) + { + // calculate the key file's canonical target path + string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true); + string fileName = Common.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture); + path = Path.Combine(directoryPath, fileName); + + // find paths that are not canonicalized + if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || + path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || + path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || + path.StartsWith("TARGETDIR", StringComparison.Ordinal) || + path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || + path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) + { + Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path)); + } + + // if component has more than one file, the key path must be versioned + if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) + { + Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers)); + } + } + else + { + // not a key path, so it must be an unversioned file if component has more than one file + if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) + { + Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers)); + } + } + } + + // if the rules were followed, reward with a generated guid + if (!Messaging.Instance.EncounteredError) + { + componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant(); + } + } + } + } + } + } + + /// + /// Creates instance transform substorages in the output. + /// + /// Output containing instance transform definitions. + private void CreateInstanceTransforms(Output output) + { + // Create and add substorages for instance transforms. + Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"]; + if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count) + { + string targetProductCode = null; + string targetUpgradeCode = null; + string targetProductVersion = null; + + Table targetSummaryInformationTable = output.Tables["_SummaryInformation"]; + Table targetPropertyTable = output.Tables["Property"]; + + // Get the data from target database + foreach (Row propertyRow in targetPropertyTable.Rows) + { + if ("ProductCode" == (string)propertyRow[0]) + { + targetProductCode = (string)propertyRow[1]; + } + else if ("ProductVersion" == (string)propertyRow[0]) + { + targetProductVersion = (string)propertyRow[1]; + } + else if ("UpgradeCode" == (string)propertyRow[0]) + { + targetUpgradeCode = (string)propertyRow[1]; + } + } + + // Index the Instance Component Rows. + Dictionary instanceComponentGuids = new Dictionary(); + Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"]; + if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count) + { + foreach (Row row in targetInstanceComponentTable.Rows) + { + // Build up all the instances, we'll get the Components rows from the real Component table. + instanceComponentGuids.Add((string)row[0], null); + } + + Table targetComponentTable = output.Tables["Component"]; + foreach (ComponentRow componentRow in targetComponentTable.Rows) + { + string component = (string)componentRow[0]; + if (instanceComponentGuids.ContainsKey(component)) + { + instanceComponentGuids[component] = componentRow; + } + } + } + + // Generate the instance transforms + foreach (Row instanceRow in wixInstanceTransformsTable.Rows) + { + string instanceId = (string)instanceRow[0]; + + Output instanceTransform = new Output(instanceRow.SourceLineNumbers); + instanceTransform.Type = OutputType.Transform; + instanceTransform.Codepage = output.Codepage; + + Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + string targetPlatformAndLanguage = null; + + foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows) + { + if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE + { + targetPlatformAndLanguage = (string)summaryInformationRow[1]; + } + + // Copy the row's data to the transform. + Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null); + copyOfSummaryRow[0] = summaryInformationRow[0]; + copyOfSummaryRow[1] = summaryInformationRow[1]; + } + + // Modify the appropriate properties. + Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); + + // Change the ProductCode property + string productCode = (string)instanceRow[2]; + if ("*" == productCode) + { + productCode = Common.GenerateGuid(); + } + + Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + productCodeRow.Operation = RowOperation.Modify; + productCodeRow.Fields[1].Modified = true; + productCodeRow[0] = "ProductCode"; + productCodeRow[1] = productCode; + + // Change the instance property + Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + instanceIdRow.Operation = RowOperation.Modify; + instanceIdRow.Fields[1].Modified = true; + instanceIdRow[0] = (string)instanceRow[1]; + instanceIdRow[1] = instanceId; + + if (null != instanceRow[3]) + { + // Change the ProductName property + Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + productNameRow.Operation = RowOperation.Modify; + productNameRow.Fields[1].Modified = true; + productNameRow[0] = "ProductName"; + productNameRow[1] = (string)instanceRow[3]; + } + + if (null != instanceRow[4]) + { + // Change the UpgradeCode property + Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + upgradeCodeRow.Operation = RowOperation.Modify; + upgradeCodeRow.Fields[1].Modified = true; + upgradeCodeRow[0] = "UpgradeCode"; + upgradeCodeRow[1] = instanceRow[4]; + + // Change the Upgrade table + Table targetUpgradeTable = output.Tables["Upgrade"]; + if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) + { + string upgradeId = (string)instanceRow[4]; + Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); + foreach (Row row in targetUpgradeTable.Rows) + { + // In case they are upgrading other codes to this new product, leave the ones that don't match the + // Product.UpgradeCode intact. + if (targetUpgradeCode == (string)row[0]) + { + Row upgradeRow = upgradeTable.CreateRow(null); + upgradeRow.Operation = RowOperation.Add; + upgradeRow.Fields[0].Modified = true; + // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. + // upgradeRow.Fields[0].PreviousData = (string)row[0]; + + // Inserting a new Upgrade record with the updated UpgradeCode + upgradeRow[0] = upgradeId; + upgradeRow[1] = row[1]; + upgradeRow[2] = row[2]; + upgradeRow[3] = row[3]; + upgradeRow[4] = row[4]; + upgradeRow[5] = row[5]; + upgradeRow[6] = row[6]; + + // Delete the old row + Row upgradeRemoveRow = upgradeTable.CreateRow(null); + upgradeRemoveRow.Operation = RowOperation.Delete; + upgradeRemoveRow[0] = row[0]; + upgradeRemoveRow[1] = row[1]; + upgradeRemoveRow[2] = row[2]; + upgradeRemoveRow[3] = row[3]; + upgradeRemoveRow[4] = row[4]; + upgradeRemoveRow[5] = row[5]; + upgradeRemoveRow[6] = row[6]; + } + } + } + } + + // If there are instance Components generate new GUIDs for them. + if (0 < instanceComponentGuids.Count) + { + Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); + foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values) + { + string guid = targetComponentRow.Guid; + if (!String.IsNullOrEmpty(guid)) + { + Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); + instanceComponentRow.Operation = RowOperation.Modify; + instanceComponentRow.Fields[1].Modified = true; + instanceComponentRow[0] = targetComponentRow[0]; + instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture); + instanceComponentRow[2] = targetComponentRow[2]; + instanceComponentRow[3] = targetComponentRow[3]; + instanceComponentRow[4] = targetComponentRow[4]; + instanceComponentRow[5] = targetComponentRow[5]; + } + } + } + + // Update the summary information + Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count); + foreach (Row row in instanceSummaryInformationTable.Rows) + { + summaryRows[row[0]] = row; + + if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) + { + row[1] = targetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) + { + row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); + } + else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) + { + row[1] = 0; + } + else if ((int)SummaryInformation.Transform.Security == (int)row[0]) + { + row[1] = "4"; + } + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + Row summaryRow = instanceSummaryInformationTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = targetPlatformAndLanguage; + } + else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) + { + Row summaryRow = instanceSummaryInformationTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = "0"; + } + else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) + { + Row summaryRow = instanceSummaryInformationTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + + output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); + } + } + } + + /// + /// Validate that there are no duplicate GUIDs in the output. + /// + /// + /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a + /// warning, as the conditions might be mutually exclusive. + /// + private void ValidateComponentGuids(Output output) + { + Table componentTable = output.Tables["Component"]; + if (null != componentTable) + { + Dictionary componentGuidConditions = new Dictionary(componentTable.Rows.Count); + + foreach (ComponentRow row in componentTable.Rows) + { + // we don't care about unmanaged components and if there's a * GUID remaining, + // there's already an error that prevented it from being replaced with a real GUID. + if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid) + { + bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); + bool allComponentsHaveConditions = thisComponentHasCondition; + + if (componentGuidConditions.ContainsKey(row.Guid)) + { + allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition; + + if (allComponentsHaveConditions) + { + Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid)); + } + else + { + Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid)); + } + } + + componentGuidConditions[row.Guid] = allComponentsHaveConditions; + } + } + } + } + + /// + /// Update Control and BBControl text by reading from files when necessary. + /// + /// Internal representation of the msi database to operate upon. + private void UpdateControlText(Output output) + { + UpdateControlTextCommand command = new UpdateControlTextCommand(); + command.BBControlTable = output.Tables["BBControl"]; + command.WixBBControlTable = output.Tables["WixBBControl"]; + command.ControlTable = output.Tables["Control"]; + command.WixControlTable = output.Tables["WixControl"]; + command.Execute(); + } + + private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory) + { + string layout = null; + + foreach (var extension in this.BackendExtensions) + { + layout = extension.ResolveMedia(mediaRow, 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; + } + + /// + /// Creates the MSI/MSM/PCP database. + /// + /// Output to create database for. + /// The database file to create. + /// Whether to keep columns added in a transform. + /// Whether to use a subdirectory based on the file name for intermediate files. + private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) + { + var command = new GenerateDatabaseCommand(); + command.Extensions = this.Extensions; + command.Output = output; + command.OutputPath = databaseFile; + command.KeepAddedColumns = keepAddedColumns; + command.UseSubDirectory = useSubdirectory; + command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.Codepage = this.Codepage; + command.Execute(); + } + } +} -- cgit v1.2.3-55-g6feb