From 63b588262aa0b7869741aa888905c1eeda7ae2f8 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 3 Sep 2022 16:08:33 -0700 Subject: Implement single pass patch build This new implementation of patching in WiX v4 creates an MSP's transforms and MSP file in a single pass. This single pass allows the build to use MSI as the source of files for diffing purposes. Completes 6401 Fixes 4629 --- src/api/wix/WixToolset.Data/ErrorMessages.cs | 3 +- .../Symbols/WixPatchBaselineSymbol.cs | 8 - .../WixToolset.Extensibility/Data/IBindContext.cs | 5 + .../WixToolset.Extensibility/Data/IFileFacade.cs | 48 +-- .../Data/IWindowsInstallerDecompileContext.cs | 5 - .../Services/IBackendHelper.cs | 24 -- .../Services/IFileResolver.cs | 38 ++ .../Services/IWindowsInstallerBackendHelper.cs | 17 + .../ExtensibilityServices/BurnBackendHelper.cs | 15 - .../Bind/AddCreateFoldersCommand.cs | 2 +- .../Bind/AttachPatchTransformsCommand.cs | 33 +- .../Bind/BindDatabaseCommand.cs | 199 +++++----- .../Bind/CabinetBuilder.cs | 24 +- .../Bind/CabinetWorkItem.cs | 10 +- .../Bind/CreateCabinetsCommand.cs | 17 +- .../Bind/CreateInstanceTransformsCommand.cs | 10 +- .../Bind/CreatePatchTransformsCommand.cs | 100 +++-- .../CreateWindowsInstallerDataFromIRCommand.cs | 5 +- .../Bind/DataLoader.cs | 49 +++ .../Bind/ExtractMergeModuleFilesCommand.cs | 10 +- .../Bind/GenerateTransformCommand.cs | 61 +-- .../Bind/GetFileFacadesCommand.cs | 7 +- .../Bind/GetFileFacadesFromTransforms.cs | 15 +- .../Bind/MergeModulesCommand.cs | 14 +- .../Bind/ProcessPropertiesCommand.cs | 22 -- .../Bind/UpdateFileFacadesCommand.cs | 210 +++++------ .../Bind/UpdateSymbolsWithFileFacadesCommand.cs | 73 ++++ .../Bind/UpdateTransformsWithFileFacades.cs | 331 ++++++++-------- .../CommandLine/TransformSubcommand.cs | 83 ++--- src/wix/WixToolset.Core.WindowsInstaller/Differ.cs | 38 +- .../ExtensibilityServices/FileFacade.cs | 82 ++++ .../WindowsInstallerBackendHelper.cs | 11 +- .../WixToolset.Core.WindowsInstaller/MsiBackend.cs | 4 - .../WixToolset.Core.WindowsInstaller/MspBackend.cs | 96 +---- .../Unbind/ExtractCabinetsCommand.cs | 85 +++-- .../Unbind/UnbindDatabaseCommand.cs | 415 ++++++++++++--------- .../Unbind/UnbindMsiOrMsmCommand.cs | 72 ---- .../Unbind/UnbindTransformCommand.cs | 14 +- .../WindowsInstallerDecompiler.cs | 74 ++-- .../WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 5 +- src/wix/WixToolset.Core/Bind/FileResolver.cs | 207 ---------- .../WixToolset.Core/Bind/ResolveFieldsCommand.cs | 235 ++++-------- src/wix/WixToolset.Core/BindContext.cs | 2 +- .../WixToolset.Core/CommandLine/BuildCommand.cs | 25 +- src/wix/WixToolset.Core/Compiler.cs | 27 +- .../ExtensibilityServices/BackendHelper.cs | 17 - .../ExtensibilityServices/FileFacade.cs | 175 --------- .../ExtensibilityServices/FileResolver.cs | 165 ++++++++ src/wix/WixToolset.Core/Librarian.cs | 8 +- src/wix/WixToolset.Core/Resolver.cs | 41 +- .../WixToolset.Core/WixToolsetServiceProvider.cs | 1 + .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 180 +++++++-- .../PatchTemplatePackage/.baseline-data/A.txt | 2 +- .../.update-data-alternative/A.txt | 3 + .../PatchTemplatePackage/.update-data/A.txt | 4 +- .../TestData/PatchUsingAdminImages/Patch.wxs | 16 + .../PatchWithFileChangesUsingMsi/Patch.wxs | 16 + 57 files changed, 1645 insertions(+), 1813 deletions(-) create mode 100644 src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs delete mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs delete mode 100644 src/wix/WixToolset.Core/Bind/FileResolver.cs delete mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/FileResolver.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs (limited to 'src') diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs index 40378a2e..77ce73aa 100644 --- a/src/api/wix/WixToolset.Data/ErrorMessages.cs +++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs @@ -651,7 +651,8 @@ namespace WixToolset.Data public static Message FileNotFound(SourceLineNumber sourceLineNumbers, string file, string fileType, IEnumerable checkedPaths) { var combinedCheckedPaths = String.Join(", ", checkedPaths); - return Message(sourceLineNumbers, Ids.FileNotFound, "The system cannot find the file '{0}' with type '{1}'. The following paths were checked: {2}", file, fileType, combinedCheckedPaths); + var withType = String.IsNullOrEmpty(fileType) ? String.Empty : $" with type '{fileType}'"; + return Message(sourceLineNumbers, Ids.FileNotFound, "The system cannot find the file '{0}'{1}. The following paths were checked: {2}", file, withType, combinedCheckedPaths); } public static Message FileOrDirectoryPathRequired(string parameter) diff --git a/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs index d7295424..cbf51e2e 100644 --- a/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs +++ b/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs @@ -14,7 +14,6 @@ namespace WixToolset.Data new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.ValidationFlags), IntermediateFieldType.Number), new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.BaselineFile), IntermediateFieldType.Path), new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.UpdateFile), IntermediateFieldType.Path), - new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.TransformFile), IntermediateFieldType.Path), }, typeof(WixPatchBaselineSymbol)); } @@ -28,7 +27,6 @@ namespace WixToolset.Data.Symbols ValidationFlags, BaselineFile, UpdateFile, - TransformFile, } public class WixPatchBaselineSymbol : IntermediateSymbol @@ -66,11 +64,5 @@ namespace WixToolset.Data.Symbols get => this.Fields[(int)WixPatchBaselineSymbolFields.UpdateFile].AsPath(); set => this.Set((int)WixPatchBaselineSymbolFields.UpdateFile, value); } - - public IntermediateFieldPathValue TransformFile - { - get => this.Fields[(int)WixPatchBaselineSymbolFields.TransformFile].AsPath(); - set => this.Set((int)WixPatchBaselineSymbolFields.TransformFile, value); - } } } diff --git a/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs b/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs index 671da292..9d663c65 100644 --- a/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs +++ b/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs @@ -17,6 +17,11 @@ namespace WixToolset.Extensibility.Data /// IServiceProvider ServiceProvider { get; } + /// + /// Bind paths used during resolution. + /// + IReadOnlyCollection BindPaths { get; set; } + /// /// Counnt of threads to use in cabbing. /// diff --git a/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs b/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs index fea00d4e..8a9e3fee 100644 --- a/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs +++ b/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs @@ -5,33 +5,12 @@ namespace WixToolset.Extensibility.Data using System.Collections.Generic; using WixToolset.Data; using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller.Rows; /// - /// Interface that provides a common facade over FileSymbol and FileRow. + /// Interface that provides a common facade over file information. /// public interface IFileFacade { - /// - /// Reference to assembly application for this file. - /// - string AssemblyApplicationFileRef { get; } - - /// - /// Reference to assembly manifest for this file. - /// - string AssemblyManifestFileRef { get; } - - /// - /// List of assembly name values in the file. - /// - List AssemblyNames { get; set; } - - /// - /// Optionally indicates what sort of assembly the file is. - /// - AssemblyType? AssemblyType { get; } - /// /// Component containing the file. /// @@ -57,21 +36,6 @@ namespace WixToolset.Extensibility.Data /// int FileSize { get; set; } - /// - /// Indicates whether the file came from a merge module. - /// - bool FromModule { get; } - - /// - /// Indicates whether the file came from a transform. - /// - bool FromTransform { get; } - - /// - /// Hash symbol of the file. - /// - MsiFileHashSymbol Hash { get; set; } - /// /// Underlying identifier of the file. /// @@ -118,9 +82,13 @@ namespace WixToolset.Extensibility.Data string Version { get; set; } /// - /// Gets the underlying FileRow if one is present. + /// Calculated hash of the file. + /// + MsiFileHashSymbol MsiFileHashSymbol { get; set; } + + /// + /// Assembly names found in the file. /// - /// FileRow if one is present, otherwise throws. - FileRow GetFileRow(); + ICollection AssemblyNameSymbols { get; } } } diff --git a/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs b/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs index 27d30a5a..7b974942 100644 --- a/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs +++ b/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs @@ -62,11 +62,6 @@ namespace WixToolset.Extensibility.Data /// string IntermediateFolder { get; set; } - /// - /// Gets or sets whether the decompiler admin image. - /// - bool IsAdminImage { get; set; } - /// /// Gets or sets where to output the result. /// diff --git a/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs b/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs index eff42b99..8bb1b2d6 100644 --- a/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs +++ b/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs @@ -5,8 +5,6 @@ namespace WixToolset.Extensibility.Services using System; using System.Collections.Generic; using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility.Data; /// @@ -14,28 +12,6 @@ namespace WixToolset.Extensibility.Services /// public interface IBackendHelper : ILayoutServices { - /// - /// Creates a file facade from a FileSymbol and possible AssemblySymbol. - /// - /// FileSymbol backing the facade. - /// AssemblySymbol backing the facade. - /// - IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly); - - /// - /// Creates a file facade from a File row. - /// - /// FileRow - /// New IFileFacade. - IFileFacade CreateFileFacade(FileRow fileRow); - - /// - /// Creates a file facade from a Merge Module's File symbol. - /// - /// FileSymbol created from a Merge Module. - /// New IFileFacade. - IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol); - /// /// Creates a MSI compatible GUID. /// diff --git a/src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs b/src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs new file mode 100644 index 00000000..2804cc28 --- /dev/null +++ b/src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs @@ -0,0 +1,38 @@ +// 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.Extensibility.Services +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + /// + /// Interface to resolve file paths using extensions and bind paths. + /// + public interface IFileResolver + { + /// + /// Resolves the source path of a file using binder extensions. + /// + /// Original source value. + /// Extensions used to resolve the file path. + /// Collection of bind paths for the binding stage. + /// Optional source line of source file being resolved. + /// Optional type of source file being resolved. + /// Should return a valid path for the stream to be imported. + string ResolveFile(string source, IEnumerable librarianExtensions, IEnumerable bindPaths, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition); + + /// + /// Resolves the source path of a file using binder extensions. + /// + /// Original source value. + /// Extensions used to resolve the file path. + /// Collection of bind paths for the binding stage. + /// The binding stage used to determine what collection of bind paths will be used + /// Optional source line of source file being resolved. + /// Optional type of source file being resolved. + /// Optional collection of paths already checked. + /// Should return a valid path for the stream to be imported. + string ResolveFile(string source, IEnumerable resolverExtensions, IEnumerable bindPaths, BindStage bindStage, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, IEnumerable alreadyCheckedPaths = null); + } +} diff --git a/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs b/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs index 81325131..2216e957 100644 --- a/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs +++ b/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs @@ -3,13 +3,30 @@ namespace WixToolset.Extensibility.Services { using WixToolset.Data; + using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; /// /// Interface provided to help Windows Installer backend extensions. /// public interface IWindowsInstallerBackendHelper : IBackendHelper { + /// + /// Creates a file facade from a FileSymbol. + /// + /// FileSymbol backing the facade. + /// + IFileFacade CreateFileFacade(FileSymbol file); + + /// + /// Creates a file facade from a File row. + /// + /// FileRow + /// New IFileFacade. + IFileFacade CreateFileFacade(FileRow fileRow); + /// /// Creates a in the specified table. /// diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs index 93e7fc20..f2b3587d 100644 --- a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs @@ -35,21 +35,6 @@ namespace WixToolset.Core.Burn.ExtensibilityServices #region IBackendHelper interfaces - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) - { - return this.backendHelper.CreateFileFacade(file, assembly); - } - - public IFileFacade CreateFileFacade(FileRow fileRow) - { - return this.backendHelper.CreateFileFacade(fileRow); - } - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) - { - return this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); - } - public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) { return this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs index c4fddb3e..44b66cea 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs @@ -35,4 +35,4 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } } -} \ No newline at end of file +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs index 66078d8d..6d37fdc2 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs @@ -145,8 +145,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; // Ensure that files are sequenced after the last file in any transform. - var transformMediaTable = mainTransform.Transform.Tables["Media"]; - if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) + if (mainTransform.Transform.Tables.TryGetTable("Media", out var transformMediaTable)) { foreach (MediaRow transformMediaRow in transformMediaTable.Rows) { @@ -370,16 +369,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind { if (transform.TryGetTable(tableName, out var table)) { - foreach (var row in table.Rows) + foreach (var row in table.Rows.Where(r => r.Operation == RowOperation.Add)) { - if (row.Operation == RowOperation.Add) - { - success = false; + success = false; - var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; + var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; - this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); - } + this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); } } } @@ -925,7 +921,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind for (var i = 0; i < propertyTable.Rows.Count; ++i) { var propertyRow = propertyTable.Rows[i]; - var property = (string)propertyRow[0]; + var property = propertyRow.FieldAsString(0); if ("ProductCode" == property) { @@ -1173,10 +1169,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); - foreach (FileRow mainFileRow in mainFileTable.Rows) + foreach (var mainFileRow in mainFileTable.Rows.Cast()) { - // Set File.Sequence to non null to satisfy transform bind. + // Set File.Sequence to non null to satisfy transform bind and suppress any + // change to File.Sequence to avoid bloat. mainFileRow.Sequence = 1; + mainFileRow.Fields[7].Modified = false; + + // Override authored media to the media provided in the patch. + mainFileRow.DiskId = mediaSymbol.DiskId; // Delete's don't need rows in the paired transform. if (mainFileRow.Operation == RowOperation.Delete) @@ -1188,13 +1189,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind pairedFileRow.Operation = RowOperation.Modify; mainFileRow.CopyTo(pairedFileRow); - // Override authored media for patch bind. - mainFileRow.DiskId = mediaSymbol.DiskId; - - // Suppress any change to File.Sequence to avoid bloat. - mainFileRow.Fields[7].Modified = false; - - // Force File row to appear in the transform. + // Force modified File rows to appear in the transform. switch (mainFileRow.Operation) { case RowOperation.Modify: diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index b849aea5..41559f8b 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -21,7 +21,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // 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, IEnumerable subStorages = null) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable patchSubStorages = null) { this.ServiceProvider = context.ServiceProvider; @@ -47,7 +47,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.ResolvedLcid = context.ResolvedLcid; this.SuppressLayout = context.SuppressLayout; - this.SubStorages = subStorages; + this.PatchSubStorages = patchSubStorages; this.BackendExtensions = backendExtension; } @@ -76,7 +76,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IEnumerable BackendExtensions { get; } - private IEnumerable SubStorages { get; } + private IEnumerable PatchSubStorages { get; } private Intermediate Intermediate { get; } @@ -180,35 +180,42 @@ namespace WixToolset.Core.WindowsInstaller.Bind 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 + // Add missing CreateFolder symbols to null-keypath components. + { + var command = new AddCreateFoldersCommand(section); + command.Execute(); + } if (this.Messaging.EncounteredError) { return null; } - this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); - this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); + // 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(); + } + } + + // Process SoftwareTags in MSI packages. + if (SectionType.Product == section.Type) + { + var softwareTags = section.Symbols.OfType().ToList(); + + if (softwareTags.Any()) + { + var command = new ProcessPackageSoftwareTagsCommand(section, this.WindowsInstallerBackendHelper, softwareTags, this.IntermediateFolder); + command.Execute(); + + trackedFiles.AddRange(command.TrackedFiles); + } + } // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). { @@ -217,21 +224,33 @@ namespace WixToolset.Core.WindowsInstaller.Bind trackedFiles.AddRange(extractedFiles); } + // Update symbols that reference text files on disk. Some of those files may have come from .wixlibs and WixExtensions + // extracted above. + { + var command = new UpdateFromTextFilesCommand(this.Messaging, section); + command.Execute(); + } + + this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); + this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); + // This must occur after all variables and source paths have been resolved. - List fileFacades; - if (SectionType.Patch == section.Type) + List allFileFacades; + List fileFacadesFromIntermediate; + List fileFacadesFromModule = null; + if (section.Type == SectionType.Patch) { - var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages); + var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.PatchSubStorages); command.Execute(); - fileFacades = command.FileFacades; + allFileFacades = fileFacadesFromIntermediate = command.FileFacades; } else { var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); command.Execute(); - fileFacades = command.FileFacades; + allFileFacades = fileFacadesFromIntermediate = command.FileFacades; } // Retrieve file information from merge modules. @@ -243,10 +262,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind { containsMergeModules = true; - var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); + var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacadesFromIntermediate, installerVersion, this.IntermediateFolder, this.SuppressLayout); command.Execute(); - fileFacades.AddRange(command.MergeModulesFileFacades); + fileFacadesFromModule = new List(command.MergeModulesFileFacades); + allFileFacades.AddRange(fileFacadesFromModule); trackedFiles.AddRange(command.TrackedFiles); } } @@ -257,23 +277,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind 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, this.WindowsInstallerBackendHelper, softwareTags, this.IntermediateFolder); - command.Execute(); - - trackedFiles.AddRange(command.TrackedFiles); - } - } - // 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); + var command = new UpdateFileFacadesCommand(this.Messaging, section, allFileFacades, fileFacadesFromIntermediate, variableCache, overwriteHash: true); command.Execute(); } @@ -289,18 +295,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind 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(); - } - // Now that delayed fields are processed, fixup the package version (if needed) and validate it // which will short circuit duplicate errors later if the ProductVersion is invalid. if (SectionType.Product == section.Type) @@ -308,18 +302,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.ProcessProductVersion(packageSymbol, section, validate: true); } - // 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. // @@ -339,9 +321,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (reresolvedFiles.Any()) { - var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id)); + var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id)); - var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false); + var command = new UpdateFileFacadesCommand(this.Messaging, section, allFileFacades, updatedFacades, variableCache, overwriteHash: false); command.Execute(); } } @@ -363,28 +345,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - // Set generated component guids and validate all guids. - { - var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); - 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); + var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, allFileFacades); order.Execute(); - fileFacades = order.FileFacades; + allFileFacades = order.FileFacades; - var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); + var assign = new AssignMediaCommand(section, this.Messaging, allFileFacades, compressed); assign.Execute(); filesByCabinetMedia = assign.FileFacadesByCabinetMedia; uncompressedFiles = assign.UncompressedFileFacades; - var update = new UpdateMediaSequencesCommand(section, fileFacades); + var update = new UpdateMediaSequencesCommand(section, allFileFacades); update.Execute(); } @@ -394,6 +370,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind return null; } + // Copy updated file facade data back into the transforms or symbols as appropriate. + if (section.Type == SectionType.Patch) + { + var command = new UpdateTransformsWithFileFacades(this.Messaging, section, this.PatchSubStorages, tableDefinitions, allFileFacades); + command.Execute(); + } + else + { + var command = new UpdateSymbolsWithFileFacadesCommand(section, allFileFacades); + command.Execute(); + } + + // Set generated component guids and validate all guids. + { + var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); + command.Execute(); + } + // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. WindowsInstallerData data; { @@ -412,9 +406,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); suppressedTableNames = unsuppress.Execute(); } + else if (data.Type == OutputType.Product) // we can create instance transforms since Component Guids and Outputs are created. + { + var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper); + command.Execute(); + + foreach (var storage in command.SubStorages) + { + data.SubStorages.Add(storage); + } + } else if (data.Type == OutputType.Patch) { - foreach (var storage in this.SubStorages) + foreach (var storage in this.PatchSubStorages) { data.SubStorages.Add(storage); } @@ -426,13 +430,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind 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) + if (section.Type == SectionType.Patch && this.DeltaBinaryPatch) { - var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); + var command = new CreateDeltaPatchesCommand(allFileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); command.Execute(); } @@ -454,19 +454,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind 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()); @@ -491,7 +478,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { this.Messaging.Write(VerboseMessages.MergingModules()); - var command = new MergeModulesCommand(this.Messaging, this.WindowsInstallerBackendHelper, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); + var command = new MergeModulesCommand(this.Messaging, this.WindowsInstallerBackendHelper, fileFacadesFromModule, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); command.Execute(); trackedFiles.AddRange(command.TrackedFiles); @@ -518,7 +505,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var fi = new FileInfo(this.OutputPath); if (fi.Length > Int32.MaxValue) { - this.Messaging.Write(WarningMessages.WindowsInstallerFileTooLarge(null, this.OutputPath, "MSI")); + this.Messaging.Write(WarningMessages.WindowsInstallerFileTooLarge(null, this.OutputPath, data.Type.ToString())); } } catch diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs index 9acbe475..93aad0a6 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs @@ -169,18 +169,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - // create the cabinet file - var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); + // Calculate the files to be compressed into the cabinet. + var compressFiles = new List(); + + foreach (var facade in cabinetWorkItem.FileFacades.OrderBy(f => f.Sequence)) + { + var modularizedId = facade.Id + cabinetWorkItem.ModularizationSuffix; - var files = cabinetWorkItem.FileFacades - .OrderBy(f => f.Sequence) - .Select(facade => facade.Hash == null ? - new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) : - new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) - .ToList(); + var compressFile = cabinetWorkItem.HashesByFileId.TryGetValue(facade.Id, out var hash) ? + new CabinetCompressFile(facade.SourcePath, modularizedId, hash.HashPart1, hash.HashPart2, hash.HashPart3, hash.HashPart4) : + new CabinetCompressFile(facade.SourcePath, modularizedId); + compressFiles.Add(compressFile); + } + + // create the cabinet file + var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); var cab = new Cabinet(cabinetPath); - var created = cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); + var created = cab.Compress(compressFiles, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); // Best effort check to see if the cabinet is too large for the Windows Installer. try diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs index 12332c80..d9bc7a7e 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs @@ -4,6 +4,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System.Collections.Generic; using WixToolset.Data; + using WixToolset.Data.Symbols; using WixToolset.Extensibility.Data; /// @@ -17,11 +18,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// Source line number that requires the cabinet creation. /// /// The collection of files in this cabinet. + /// The hashes for unversioned files. /// The cabinet file. /// Maximum threshold for each cabinet. /// The compression level of the cabinet. /// Modularization suffix used when building a Merge Module. - public CabinetWorkItem(SourceLineNumber sourceLineNumber, int diskId, string cabinetFile, IEnumerable fileFacades, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix) + public CabinetWorkItem(SourceLineNumber sourceLineNumber, int diskId, string cabinetFile, IEnumerable fileFacades, Dictionary hashesByFileId, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix) { this.SourceLineNumber = sourceLineNumber; this.DiskId = diskId; @@ -29,6 +31,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.CompressionLevel = compressionLevel; this.ModularizationSuffix = modularizationSuffix; this.FileFacades = fileFacades; + this.HashesByFileId = hashesByFileId; this.MaxThreshold = maxThreshold; } @@ -65,6 +68,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// The collection of files in this cabinet. public IEnumerable FileFacades { get; } + /// + /// The hashes for unversioned files. + /// + public Dictionary HashesByFileId { get; } + /// /// Gets the max threshold. /// diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs index 6ed107d5..1ed2ba79 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs @@ -95,6 +95,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, maximumCabinetSizeForLargeFileSplitting, maximumUncompressedMediaSize); + var hashesByFileId = this.Section.Symbols.OfType().ToDictionary(s => s.Id.Id); + foreach (var entry in this.FileFacadesByCabinet) { var mediaSymbol = entry.Key; @@ -102,7 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium; var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory); - var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files); + var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files, hashesByFileId); if (null != cabinetWorkItem) { cabinetBuilder.Enqueue(cabinetWorkItem); @@ -140,16 +142,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind return cabbingThreadCount; } - /// - /// Creates a work item to create a cabinet. - /// - /// Windows Installer data for the current database. - /// Directory to create cabinet in. - /// Media symbol containing information about the cabinet. - /// Desired compression level. - /// Collection of files in this cabinet. - /// created CabinetWorkItem object - private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable fileFacades) + private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable fileFacades, Dictionary hashesByFileId) { CabinetWorkItem cabinetWorkItem = null; @@ -171,7 +164,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) { // Default to the threshold for best smartcabbing (makes smallest cabinet). - cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, mediaSymbol.DiskId, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix); + cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, mediaSymbol.DiskId, resolvedCabinet.Path, fileFacades, hashesByFileId, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix); } else // reuse the cabinet from the cabinet cache. { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs index d0e25571..1d480250 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs @@ -30,8 +30,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IBackendHelper BackendHelper { get; } - public void Execute() + public IReadOnlyCollection SubStorages { get; private set; } + + public IReadOnlyCollection Execute() { + var subStorages = new List(); + // Create and add substorages for instance transforms. var wixInstanceTransformsSymbols = this.Section.Symbols.OfType(); @@ -252,9 +256,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind summaryRow[1] = "4"; } - this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); + subStorages.Add(new SubStorage(instanceId, instanceTransform)); } } + + return this.SubStorages = subStorages; } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs index 5c993f63..6d5bff69 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs @@ -6,31 +6,44 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.Collections.Generic; using System.IO; using System.Linq; - using WixToolset.Core.Native.Msi; using WixToolset.Core.WindowsInstaller.Unbind; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; internal class CreatePatchTransformsCommand { - public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder) + public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, IFileResolver fileResolver, IReadOnlyCollection resolverExtensions, Intermediate intermediate, string intermediateFolder, IReadOnlyCollection bindPaths) { this.Messaging = messaging; this.BackendHelper = backendHelper; + this.PathResolver = pathResolver; + this.FileResolver = fileResolver; + this.ResolverExtensions = resolverExtensions; this.Intermediate = intermediate; this.IntermediateFolder = intermediateFolder; + this.BindPaths = bindPaths; } private IMessaging Messaging { get; } private IBackendHelper BackendHelper { get; } + private IPathResolver PathResolver { get; } + + private IFileResolver FileResolver { get; } + + private IReadOnlyCollection ResolverExtensions { get; } + private Intermediate Intermediate { get; } private string IntermediateFolder { get; } + private IReadOnlyCollection BindPaths { get; } + public IEnumerable PatchTransforms { get; private set; } public IEnumerable Execute() @@ -41,23 +54,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind foreach (var symbol in symbols) { - WindowsInstallerData transform; - - if (symbol.TransformFile is null) - { - var baselineData = this.GetData(symbol.BaselineFile.Path); - var updateData = this.GetData(symbol.UpdateFile.Path); + var targetData = this.GetWindowsInstallerData(symbol.BaselineFile.Path, BindStage.Target); + var updatedData = this.GetWindowsInstallerData(symbol.UpdateFile.Path, BindStage.Updated); - var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); - transform = command.Execute(); - } - else - { - var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path. - - var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder); - transform = command.Execute(); - } + var command = new GenerateTransformCommand(this.Messaging, targetData, updatedData, preserveUnchangedRows: true, showPedanticMessages: false); + var transform = command.Execute(); patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); } @@ -67,26 +68,61 @@ namespace WixToolset.Core.WindowsInstaller.Bind return this.PatchTransforms; } - private WindowsInstallerData GetData(string path) + private WindowsInstallerData GetWindowsInstallerData(string path, BindStage stage) { - var ext = Path.GetExtension(path); - - if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase)) + if (DataLoader.TryLoadWindowsInstallerData(path, true, out var data)) { - using (var database = new Database(path, OpenDatabase.ReadOnly)) - { - var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. + // Re-resolve file paths only when loading from .wixpdb. + this.ReResolveWindowsInstallerData(data, stage); + } + else + { + var stageFolder = $"_{stage.ToString().ToLowerInvariant()}_msi"; + var exportBasePath = Path.Combine(this.IntermediateFolder, stageFolder); + var extractFilesFolder = Path.Combine(exportBasePath, "File"); + + var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, OutputType.Product, exportBasePath, extractFilesFolder, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false); + data = command.Execute(); + } - var isAdminImage = false; // TODO: need a better way to set this + return data; + } - var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); - return command.Execute(); - } + private void ReResolveWindowsInstallerData(WindowsInstallerData data, BindStage stage) + { + var bindPaths = this.BindPaths.Where(b => b.Stage == stage).ToList(); + + if (bindPaths.Count == 0) + { + return; } - else // assume .wixpdb (or .wixout) + + foreach (var table in data.Tables) { - var data = WindowsInstallerData.Load(path, true); - return data; + foreach (var row in table.Rows) + { + foreach (var field in row.Fields.Where(f => f.Column.Type == ColumnType.Object)) + { + if (field.PreviousData != null) + { + try + { + var originalPath = field.AsString(); + + var resolvedPath = this.FileResolver.ResolveFile(field.PreviousData, this.ResolverExtensions, bindPaths, stage, row.SourceLineNumbers, null); + + if (!String.Equals(originalPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) + { + field.Data = resolvedPath; + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + } + } + } } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs index 60317cd9..4d99ff40 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs @@ -662,10 +662,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind row.FileSize = symbol.FileSize; row.Version = symbol.Version; row.Language = symbol.Language; - row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here row.Sequence = symbol.Sequence; + row.DiskId = symbol.DiskId ?? throw new InvalidDataException("FileSymbol.DiskId should have been initialized before creating WindowsInstallerData from IntermediateRepresentation."); row.Source = symbol.Source.Path; + var previousSourceField = symbol.Fields[(int)FileSymbolFields.Source]?.PreviousValue; + row.PreviousSource = previousSourceField?.AsPath().Path; + var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0; diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs new file mode 100644 index 00000000..6214bbdb --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs @@ -0,0 +1,49 @@ +// 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.IO; + using WixToolset.Data.WindowsInstaller; + + internal class DataLoader + { + public static bool TryLoadWindowsInstallerData(string path, out WindowsInstallerData data) + { + return TryLoadWindowsInstallerData(path, false, out data); + } + + public static bool TryLoadWindowsInstallerData(string path, bool suppressVersionCheck, out WindowsInstallerData data) + { + data = null; + + var extension = Path.GetExtension(path); + + // If the path is _not_ obviously a Windows Installer database, let's try opening it as + // our own data file format. + if (!extension.Equals(".msi", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".msm", StringComparison.OrdinalIgnoreCase)) + { + (data, _) = LoadWindowsInstallerDataSafely(path, suppressVersionCheck); + } + + return data != null; + } + + public static (WindowsInstallerData, Exception) LoadWindowsInstallerDataSafely(string path, bool suppressVersionCheck = false) + { + WindowsInstallerData data = null; + Exception exception = null; + + try + { + data = WindowsInstallerData.Load(path, suppressVersionCheck); + } + catch (Exception e) + { + exception = e; + } + + return (data, exception); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs index 06168727..94ed0afc 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs @@ -22,12 +22,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class ExtractMergeModuleFilesCommand { - public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable wixMergeSymbols, IEnumerable fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout) + public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable wixMergeSymbols, IEnumerable fileFacadesFromIntermediate, int installerVersion, string intermediateFolder, bool suppressLayout) { this.Messaging = messaging; this.BackendHelper = backendHelper; this.WixMergeSymbols = wixMergeSymbols; - this.FileFacades = fileFacades; + this.FileFacadesFromIntermediate = fileFacadesFromIntermediate; this.OutputInstallerVersion = installerVersion; this.IntermediateFolder = intermediateFolder; this.SuppressLayout = suppressLayout; @@ -39,7 +39,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IEnumerable WixMergeSymbols { get; } - private IEnumerable FileFacades { get; } + private IEnumerable FileFacadesFromIntermediate { get; } private int OutputInstallerVersion { get; } @@ -65,7 +65,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let // this case be slightly more expensive because the cost of maintaining an indexed file row collection // is a lot more costly for the common cases. - var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); + var indexedFileFacades = this.FileFacadesFromIntermediate.ToDictionary(f => f.Id, StringComparer.Ordinal); foreach (var wixMergeRow in this.WixMergeSymbols) { @@ -115,7 +115,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind fileSymbol.DiskId = wixMergeRow.DiskId; fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) }; - var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + var mergeModuleFileFacade = this.BackendHelper.CreateFileFacade(fileSymbol); // If case-sensitive collision with another merge module or a user-authored file identifier. if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs index 575065bb..92a0e11f 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs @@ -16,7 +16,7 @@ namespace WixToolset.Core.WindowsInstaller /// internal class GenerateTransformCommand { - private const char sectionDelimiter = '/'; + private const char SectionDelimiter = '/'; private readonly IMessaging messaging; private SummaryInformationStreams transformSummaryInfo; @@ -38,22 +38,10 @@ namespace WixToolset.Core.WindowsInstaller private TransformFlags ValidationFlags { get; } - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. private bool ShowPedanticMessages { get; } - /// - /// Gets or sets the option to suppress keeping special rows. - /// - /// The option to suppress keeping special rows. private bool SuppressKeepingSpecialRows { get; } - /// - /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. - /// - /// The option to keep all rows including unchanged rows. private bool PreserveUnchangedRows { get; } public WindowsInstallerData Transform { get; private set; } @@ -124,7 +112,7 @@ namespace WixToolset.Core.WindowsInstaller foreach (var updatedRow in updatedTable.Rows) { updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId; addedTable.Rows.Add(updatedRow); } } @@ -152,27 +140,8 @@ namespace WixToolset.Core.WindowsInstaller if (null != primaryKey) { - if (index.TryGetValue(primaryKey, out var collisionRow)) + if (index.ContainsKey(primaryKey)) { -#if TODO_PATCH // This case doesn't seem like it can happen any longer. - // Overriding WixActionRows have a primary key defined and take precedence in the index. - if (row is WixActionRow actionRow) - { - // If the current row is not overridable, see if the indexed row is. - if (!actionRow.Overridable) - { - if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable) - { - // The indexed key is overridable and should be replaced. - index[primaryKey] = actionRow; - } - } - - // If we got this far, the row does not need to be indexed. - return; - } -#endif - if (this.ShowPedanticMessages) { this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); @@ -208,7 +177,7 @@ namespace WixToolset.Core.WindowsInstaller else if (null == updatedRow) { targetRow.Operation = RowOperation.Delete; - targetRow.SectionId += sectionDelimiter; + targetRow.SectionId += SectionDelimiter; comparedRow = targetRow; keepRow = true; @@ -219,10 +188,10 @@ namespace WixToolset.Core.WindowsInstaller updatedRow.Operation = RowOperation.None; if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) { - // ignore rows that shouldn't be in a transform + // Include only summary information rows that are allowed in a transform. if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) { - updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + updatedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId; comparedRow = updatedRow; keepRow = true; } @@ -273,14 +242,14 @@ namespace WixToolset.Core.WindowsInstaller updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; - // always keep a copy of the previous data even if they are identical - // This makes diff.wixmst clean and easier to control patch logic + // Always keep a copy of the previous data even if they are identical. + // This makes diff data clean and easier to control in patch logic. updatedObjectField.PreviousData = (string)targetObjectField.Data; - // always remember the unresolved data for target build + // Always remember the unresolved data for target build. updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData; - // keep rows containing object fields so the files can be compared in the binder + // Keep rows containing object fields so the files can be compared later. keepRow = !this.SuppressKeepingSpecialRows; } else @@ -305,7 +274,7 @@ namespace WixToolset.Core.WindowsInstaller if (keepRow) { comparedRow = updatedRow; - comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + comparedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId; } } } @@ -333,9 +302,6 @@ namespace WixToolset.Core.WindowsInstaller } else // possibly modified table. { - var updatedPrimaryKeys = new Dictionary(); - var targetPrimaryKeys = new Dictionary(); - // compare the table definitions if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) { @@ -344,6 +310,9 @@ namespace WixToolset.Core.WindowsInstaller } else { + var updatedPrimaryKeys = new Dictionary(); + var targetPrimaryKeys = new Dictionary(); + this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); // diff the target and updated rows @@ -371,7 +340,7 @@ namespace WixToolset.Core.WindowsInstaller var updatedRow = updatedPrimaryKeyEntry.Value; updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId; rows.Add(updatedRow); } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs index 949d5e18..fa072293 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs @@ -2,9 +2,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { - using System; using System.Collections.Generic; - using System.Globalization; using System.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; @@ -29,20 +27,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var facades = new List(); - var assemblyFile = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); #if TODO_PATCHING_DELTA //var deltaPatchFiles = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); #endif foreach (var file in this.Section.Symbols.OfType()) { - assemblyFile.TryGetValue(file.Id.Id, out var assembly); - #if TODO_PATCHING_DELTA //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); // TODO: should we be passing along delta information to the file facade? Probably, right? #endif - var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly); + var fileFacade = this.BackendHelper.CreateFileFacade(file); facades.Add(fileFacade); } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs index 441038ec..7c71e238 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs @@ -37,8 +37,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind var patchMediaFileRows = new Dictionary>(); - //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); - // Index paired transforms by name without their "#" prefix. var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix)).ToDictionary(s => s.Name, s => s.Data); @@ -57,7 +55,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var pairedTransform = pairedTransforms[PatchConstants.PairedPatchTransformPrefix + substorage.Name]; var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); - foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) + foreach (var mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete).Cast()) { var mainFileId = mainFileRow.File; @@ -89,8 +87,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else { - // TODO: should this entire condition be placed in the binder file manager? - if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/ + if ( +#if TODO_PATCHING_DELTA + (0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && +#endif !this.FileSystemManager.CompareFiles(objectField.PreviousData, objectField.Data.ToString())) { // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. @@ -133,11 +133,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind patchMediaFileRows.Add(diskId, mediaFileRows); } - var patchFileRow = mediaFileRows.Get(mainFileId); - - if (null == patchFileRow) + if (!mediaFileRows.TryGetValue(mainFileId, out var patchFileRow)) { - //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); mainFileRow.CopyTo(patchFileRow); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs index 7e6a7de0..5f21f496 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.Runtime.InteropServices; using System.Text; using System.Threading; - using WixToolset.Core.Native; using WixToolset.Core.Native.Msi; using WixToolset.Core.Native.Msm; using WixToolset.Data; @@ -24,11 +23,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class MergeModulesCommand { - public MergeModulesCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable fileFacades, IntermediateSection section, IEnumerable suppressedTableNames, string outputPath, string intermediateFolder) + public MergeModulesCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable fileFacadesFromModule, IntermediateSection section, IEnumerable suppressedTableNames, string outputPath, string intermediateFolder) { this.Messaging = messaging; this.BackendHelper = backendHelper; - this.FileFacades = fileFacades; + this.FileFacadesFromModule = fileFacadesFromModule; this.Section = section; this.SuppressedTableNames = suppressedTableNames ?? Array.Empty(); this.OutputPath = outputPath; @@ -39,7 +38,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IBackendHelper BackendHelper { get; } - private IEnumerable FileFacades { get; } + private IEnumerable FileFacadesFromModule { get; } private IntermediateSection Section { get; } @@ -280,13 +279,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) { - foreach (var file in this.FileFacades) + foreach (var file in this.FileFacadesFromModule) { - if (!file.FromModule) - { - continue; - } - using (var record = new Record(1)) { record.SetString(1, file.Id); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs index 217609be..1f09a267 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs @@ -47,28 +47,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) { propertySymbol.Value = this.BackendHelper.CreateGuid(); - -#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 } else if ("ProductLanguage" == propertySymbol.Id.Id) { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 8043ffa8..f2f8e126 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -19,11 +19,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class UpdateFileFacadesCommand { - public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable fileFacades, IEnumerable updateFileFacades, IDictionary variableCache, bool overwriteHash) + public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable allFileFacades, IEnumerable updateFileFacades, IDictionary variableCache, bool overwriteHash) { this.Messaging = messaging; this.Section = section; - this.FileFacades = fileFacades; + this.AllFileFacades = allFileFacades; this.UpdateFileFacades = updateFileFacades; this.VariableCache = variableCache; this.OverwriteHash = overwriteHash; @@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IntermediateSection Section { get; } - private IEnumerable FileFacades { get; } + private IEnumerable AllFileFacades { get; } private IEnumerable UpdateFileFacades { get; } @@ -43,15 +43,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { + var assemblySymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); var assemblyNameSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) { - this.UpdateFileFacade(file, assemblyNameSymbols); + this.UpdateFileFacade(file, assemblySymbols, assemblyNameSymbols); } } - private void UpdateFileFacade(IFileFacade facade, Dictionary assemblyNameSymbols) + private void UpdateFileFacade(IFileFacade facade, Dictionary assemblySymbols, Dictionary assemblyNameSymbols) { FileInfo fileInfo = null; try @@ -124,7 +125,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. - if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) + if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) { this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); } @@ -153,16 +154,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - if (null == facade.Hash) + // Remember the hash symbol for use later. + facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier) { - facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); - } - - facade.Hash.Options = 0; - facade.Hash.HashPart1 = hash[0]; - facade.Hash.HashPart2 = hash[1]; - facade.Hash.HashPart3 = hash[2]; - facade.Hash.HashPart4 = hash[3]; + Options = 0, + HashPart1 = hash[0], + HashPart2 = hash[1], + HashPart3 = hash[2], + HashPart4 = hash[3], + }; } } else // update the file row with the version and language information. @@ -173,7 +173,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { facade.Version = version; } - else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. + else if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. { // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching // the version value). We didn't find it so, we will override the default version they provided with the actual @@ -204,108 +204,104 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty; } - // If this is a CLR assembly, load the assembly and get the assembly name information - if (AssemblyType.DotNetAssembly == facade.AssemblyType) + // If there is an assembly for this file. + if (assemblySymbols.TryGetValue(facade.Id, out var assemblySymbol)) { - try + // If this is a CLR assembly, load the assembly and get the assembly name information + if (AssemblyType.DotNetAssembly == assemblySymbol.Type) { - var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); + try + { + var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name); + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "culture", assemblyName.Culture); + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version); - if (!String.IsNullOrEmpty(assemblyName.Architecture)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); - } - // TODO: WiX v3 seemed to do this but not clear it should actually be done. - //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) - //{ - // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); - //} + if (!String.IsNullOrEmpty(assemblyName.Architecture)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture); + } + // TODO: WiX v3 seemed to do this but not clear it should actually be done. + //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) + //{ + // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); + //} - if (assemblyName.StrongNamedSigned) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); - } - else if (facade.AssemblyApplicationFileRef == null) - { - throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); - } + if (assemblyName.StrongNamedSigned) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken); + } + else if (assemblySymbol.ApplicationFileRef == null) + { + throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); + } - if (!String.IsNullOrEmpty(assemblyName.FileVersion)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); - } + if (!String.IsNullOrEmpty(assemblyName.FileVersion)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "fileVersion", assemblyName.FileVersion); + } - // add the assembly name to the information cache - if (null != this.VariableCache) + // add the assembly name to the information cache + if (null != this.VariableCache) + { + this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); + } + } + catch (WixException e) { - this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); + this.Messaging.Write(e.Error); } } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - } - else if (AssemblyType.Win32Assembly == facade.AssemblyType) - { - // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through - // all files like this. Even though this is a rare case it looks like we might be able to index the - // file earlier. - var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); - if (null == fileManifest) - { - this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); - } - - try + else if (AssemblyType.Win32Assembly == assemblySymbol.Type) { - var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); - - if (!String.IsNullOrEmpty(assemblyName.Name)) + // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through + // all files like this. Even though this is a rare case it looks like we might be able to index the + // file earlier. + var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal)); + if (null == fileManifest) { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); + this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef)); } - if (!String.IsNullOrEmpty(assemblyName.Version)) + try { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); - } + var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); - if (!String.IsNullOrEmpty(assemblyName.Type)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); - } + if (!String.IsNullOrEmpty(assemblyName.Name)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name); + } - if (!String.IsNullOrEmpty(assemblyName.Architecture)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); - } + if (!String.IsNullOrEmpty(assemblyName.Version)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version); + } + + if (!String.IsNullOrEmpty(assemblyName.Type)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "type", assemblyName.Type); + } + + if (!String.IsNullOrEmpty(assemblyName.Architecture)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture); + } - if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) + if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken); + } + } + catch (WixException e) { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); + this.Messaging.Write(e.Error); } } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } } } - /// - /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise - /// create a new row. - /// - /// MsiAssemblyName table. - /// FileFacade containing the assembly read for the MsiAssemblyName row. - /// MsiAssemblyName name. - /// MsiAssemblyName value. - private void SetMsiAssemblyName(Dictionary assemblyNameSymbols, IFileFacade facade, string name, string value) + private void SetMsiAssemblyName(Dictionary assemblyNameSymbols, IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value) { // check for null value (this can occur when grabbing the file version from an assembly without one) if (String.IsNullOrEmpty(value)) @@ -315,36 +311,32 @@ namespace WixToolset.Core.WindowsInstaller.Bind else { // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. - if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && - String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && + if ("name" == name && AssemblyType.DotNetAssembly == assemblySymbol.Type && + String.IsNullOrEmpty(assemblySymbol.ApplicationFileRef) && !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) { this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); } - // override directly authored value + // Override directly authored value, otherwise remember the gathered information on the facade for use later. var lookup = String.Concat(facade.ComponentRef, "/", name); - if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) + if (assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) { - assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) + assemblyNameSymbol.Value = value; + } + else + { + assemblyNameSymbol = new MsiAssemblyNameSymbol(assemblySymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) { ComponentRef = facade.ComponentRef, Name = name, Value = value, - }); - - if (null == facade.AssemblyNames) - { - facade.AssemblyNames = new List(); - } - - facade.AssemblyNames.Add(assemblyNameSymbol); + }; + facade.AssemblyNameSymbols.Add(assemblyNameSymbol); assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); } - assemblyNameSymbol.Value = value; - if (this.VariableCache != null) { var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs new file mode 100644 index 00000000..4b332363 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs @@ -0,0 +1,73 @@ +// 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.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class UpdateSymbolsWithFileFacadesCommand + { + private readonly IntermediateSection section; + private readonly List allFileFacades; + + public UpdateSymbolsWithFileFacadesCommand(IntermediateSection section, List allFileFacades) + { + this.section = section; + this.allFileFacades = allFileFacades; + } + + public void Execute() + { + var fileSymbolsById = this.section.Symbols.OfType().ToDictionary(f => f.Id.Id); + + foreach (var facade in this.allFileFacades) + { + if (fileSymbolsById.TryGetValue(facade.Id, out var fileSymbol)) + { + // Only update the file symbol if the facade value changed + if (fileSymbol.DiskId != facade.DiskId) + { + fileSymbol.DiskId = facade.DiskId; + } + + if (fileSymbol.FileSize != facade.FileSize) + { + fileSymbol.FileSize = facade.FileSize; + } + + if (fileSymbol.Language != facade.Language) + { + fileSymbol.Language = facade.Language; + } + + if (fileSymbol.Sequence != facade.Sequence) + { + fileSymbol.Sequence = facade.Sequence; + } + + if (fileSymbol.Version != facade.Version) + { + fileSymbol.Version = facade.Version; + } + } + + if (facade.MsiFileHashSymbol != null) + { + this.section.AddSymbol(facade.MsiFileHashSymbol); + } + + if (facade.AssemblyNameSymbols != null) + { + foreach (var assemblyNameSymbol in facade.AssemblyNameSymbols) + { + this.section.AddSymbol(assemblyNameSymbol); + } + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs index ed865450..7b6c21ef 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs @@ -4,6 +4,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System; using System.Collections.Generic; + using System.IO; using System.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; @@ -14,10 +15,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind internal class UpdateTransformsWithFileFacades { - public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable subStorages, TableDefinitionCollection tableDefinitions, IEnumerable fileFacades) + public UpdateTransformsWithFileFacades(IMessaging messaging, IntermediateSection section, IEnumerable subStorages, TableDefinitionCollection tableDefinitions, IEnumerable fileFacades) { this.Messaging = messaging; - this.Output = output; + this.Section = section; this.SubStorages = subStorages; this.TableDefinitions = tableDefinitions; this.FileFacades = fileFacades; @@ -25,7 +26,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IMessaging Messaging { get; } - private WindowsInstallerData Output { get; } + private IntermediateSection Section { get; } private IEnumerable SubStorages { get; } @@ -35,190 +36,97 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { - var fileFacadesByDiskId = new Dictionary>(); - - // Index patch file facades by diskId+fileId. - foreach (var facade in this.FileFacades) - { - if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) - { - mediaFacades = new Dictionary(); - fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); - } - - mediaFacades.Add(facade.Id, facade); - } + var fileFacadesByDiskId = this.IndexFileFacadesByDiskId(); - var patchMediaRows = new RowDictionary(this.Output.Tables["Media"]); + var mediaSymbolsByDiskId = this.Section.Symbols.OfType().ToDictionary(m => m.DiskId); // Index paired transforms by name without the "#" prefix. var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix)).ToDictionary(s => s.Name, s => s.Data); - // Copy File bind data into substorages foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix))) { var mainTransform = substorage.Data; - var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); - var pairedTransform = pairedTransforms[PatchConstants.PairedPatchTransformPrefix + substorage.Name]; - // Copy Media.LastSequence. - var pairedMediaTable = pairedTransform.Tables["Media"]; - foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) + // Update the Media.LastSequence in the paired transforms. + foreach (var pairedMediaRow in pairedTransform.Tables["Media"].Rows.Cast()) { - var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); - pairedMediaRow.LastSequence = patchMediaRow.LastSequence; + if (mediaSymbolsByDiskId.TryGetValue(pairedMediaRow.DiskId, out var mediaSymbol) && mediaSymbol.LastSequence.HasValue) + { + pairedMediaRow.LastSequence = mediaSymbol.LastSequence.Value; + } + else // TODO: This shouldn't be possible. + { + throw new InvalidDataException(); + } } // Validate file row changes for keypath-related issues this.ValidateFileRowChanges(mainTransform); - // Index File table of pairedTransform - var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); - - var mainFileTable = mainTransform.Tables["File"]; - if (null != mainFileTable) + // Copy File bind data into transforms + if (mainTransform.Tables.TryGetTable("File", out var mainFileTable)) { + // Index File table of pairedTransform + var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); + + var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); + // Remove the MsiFileHash table because it will be updated later with the final file hash for each file mainTransform.Tables.Remove("MsiFileHash"); - foreach (FileRow mainFileRow in mainFileTable.Rows) + foreach (var mainFileRow in mainFileTable.Rows.Where(r => r.Operation == RowOperation.Add || r.Operation == RowOperation.Modify).Cast()) { - if (RowOperation.Delete == mainFileRow.Operation) - { - continue; - } - else if (RowOperation.None == mainFileRow.Operation) - { - continue; - } - - // Index patch files by diskId+fileId + // TODO: Wasn't this indexing done at the top of this method? + // Index main transform files by diskId+fileId if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) { mediaFacades = new Dictionary(); fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); } - // copy data from the patch back to the transform + // Copy data from the facade back to the appropriate transform. if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) { - var patchFileRow = facade.GetFileRow(); var pairedFileRow = pairedFileRows.Get(mainFileRow.File); - for (var i = 0; i < patchFileRow.Fields.Length; i++) - { - var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; - var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; + TryModifyField(mainFileRow, 3, facade.FileSize); + + TryModifyField(mainFileRow, 4, facade.Version); + + TryModifyField(mainFileRow, 5, facade.Language); - if (1 == i) - { - // File.Component_ changes should not come from the shared file rows - // that contain the file information as each individual transform might - // have different changes (or no changes at all). - } - else if (6 == i) // File.Attributes should not changed for binary deltas - { -#if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - // File.Attribute should not change for binary deltas - pairedFileRow.Attributes = mainFileRow.Attributes; - mainFileRow.Fields[i].Modified = false; - } -#endif - } - else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform - { - // file sequence is updated in Patch table instead of File table for delta patches #if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - pairedFileRow.Fields[i].Modified = false; - } - else + // File.Attribute should not change for binary deltas, otherwise copy File Attributes from main transform row. + if (null != facade.Patch) #endif - { - pairedFileRow[i] = patchFileRow[i]; - pairedFileRow.Fields[i].Modified = true; - } - mainFileRow.Fields[i].Modified = false; - } - else if (patchValue != mainValue) - { - mainFileRow[i] = patchFileRow[i]; - mainFileRow.Fields[i].Modified = true; - if (mainFileRow.Operation == RowOperation.None) - { - mainFileRow.Operation = RowOperation.Modify; - } - } + { + TryModifyField(pairedFileRow, 6, mainFileRow.Attributes); + mainFileRow.Fields[6].Modified = false; } - // Copy MsiFileHash row for this File. - if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) +#if TODO_PATCHING_DELTA + // File.Sequence is updated in Patch table instead of File table for delta patches + if (null != facade.Patch) { - //patchHashRow = patchFileRow.Hash; - throw new NotImplementedException(); + pairedFileRow.Fields[7].Modified = false; } - - if (null != patchHashRow) + else +#endif { - var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); - var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); - for (var i = 0; i < patchHashRow.Fields.Length; i++) - { - mainHashRow[i] = patchHashRow[i]; - if (i > 1) - { - // assume all hash fields have been modified - mainHashRow.Fields[i].Modified = true; - } - } - - // assume the MsiFileHash operation follows the File one - mainHashRow.Operation = mainFileRow.Operation; + // File.Sequence is updated in pairedTransform, not mainTransform. + TryModifyField(pairedFileRow, 7, facade.Sequence); } + mainFileRow.Fields[7].Modified = false; - // copy MsiAssemblyName rows for this File -#if TODO_PATCHING - List patchAssemblyNameRows = patchFileRow.AssemblyNames; - if (null != patchAssemblyNameRows) - { - var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - foreach (var patchAssemblyNameRow in patchAssemblyNameRows) - { - // Copy if there isn't an identical modified/added row already in the transform. - var foundMatchingModifiedRow = false; - foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) - { - if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) - { - foundMatchingModifiedRow = true; - break; - } - } + this.ProcessMsiFileHash(mainTransform, mainFileRow, facade.MsiFileHashSymbol, mainMsiFileHashIndex); - if (!foundMatchingModifiedRow) - { - var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); - for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) - { - mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; - } - - // assume value field has been modified - mainAssemblyNameRow.Fields[2].Modified = true; - mainAssemblyNameRow.Operation = mainFileRow.Operation; - } - } - } -#endif + this.ProcessMsiAssemblyName(mainTransform, mainFileRow, facade); - // Add patch header for this file #if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) + // Add patch header for this file + if (null != facade.Patch) { // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); @@ -232,12 +140,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind } var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); - patchRow[0] = patchFileRow.File; - patchRow[1] = patchFileRow.Sequence; + patchRow[0] = facade.File; + patchRow[1] = facade.Sequence; - var patchFile = new FileInfo(patchFileRow.Source); + var patchFile = new FileInfo(facade.Source); patchRow[2] = (int)patchFile.Length; - patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; + patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & facade.PatchAttributes) ? 0 : 1; var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) @@ -252,13 +160,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); patchHeadersRow[0] = streamName; - patchHeadersRow[1] = patchFileRow.Patch; + patchHeadersRow[1] = facade.Patch; patchRow[5] = streamName; patchHeadersRow.Operation = RowOperation.Add; } else { - patchRow[4] = patchFileRow.Patch; + patchRow[4] = facade.Patch; } patchRow.Operation = RowOperation.Add; } @@ -270,14 +178,89 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } } + } + } + + private void ProcessMsiFileHash(WindowsInstallerData transform, FileRow fileRow, MsiFileHashSymbol msiFileHashSymbol, RowDictionary msiFileHashIndex) + { + Row msiFileHashRow = null; + + if (msiFileHashSymbol != null || msiFileHashIndex.TryGetValue(fileRow.File, out msiFileHashRow)) + { + var sourceLineNumbers = msiFileHashSymbol?.SourceLineNumbers ?? msiFileHashRow?.SourceLineNumbers; + + var transformHashTable = transform.EnsureTable(this.TableDefinitions["MsiFileHash"]); + + var transformHashRow = transformHashTable.CreateRow(sourceLineNumbers); + transformHashRow.Operation = fileRow.Operation; // Assume the MsiFileHash operation follows the File one. + + transformHashRow[0] = fileRow.File; + transformHashRow[1] = msiFileHashSymbol?.Options ?? msiFileHashRow?.Fields[1].Data; + + // Assume all hash fields have been modified. + TryModifyField(transformHashRow, 2, msiFileHashSymbol?.HashPart1 ?? msiFileHashRow?.Fields[2].Data); + TryModifyField(transformHashRow, 3, msiFileHashSymbol?.HashPart2 ?? msiFileHashRow?.Fields[3].Data); + TryModifyField(transformHashRow, 4, msiFileHashSymbol?.HashPart3 ?? msiFileHashRow?.Fields[4].Data); + TryModifyField(transformHashRow, 5, msiFileHashSymbol?.HashPart4 ?? msiFileHashRow?.Fields[5].Data); + } + } - this.Output.Tables.Remove("Media"); - this.Output.Tables.Remove("File"); - this.Output.Tables.Remove("MsiFileHash"); - this.Output.Tables.Remove("MsiAssemblyName"); + private void ProcessMsiAssemblyName(WindowsInstallerData transform, FileRow fileRow, IFileFacade facade) + { + if (facade.AssemblyNameSymbols.Count > 0) + { + var assemblyNameTable = transform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + + foreach (var assemblyNameSymbol in facade.AssemblyNameSymbols) + { + // Copy if there isn't an identical modified/added row already in the transform. + var foundMatchingModifiedRow = false; + foreach (var mainAssemblyNameRow in assemblyNameTable.Rows.Where(r => r.Operation != RowOperation.None)) + { + var component = mainAssemblyNameRow.FieldAsString(0); + var name = mainAssemblyNameRow.FieldAsString(1); + + if (assemblyNameSymbol.ComponentRef == component && assemblyNameSymbol.Name == name) + { + foundMatchingModifiedRow = true; + break; + } + } + + if (!foundMatchingModifiedRow) + { + var assemblyNameRow = assemblyNameTable.CreateRow(fileRow.SourceLineNumbers); + assemblyNameRow[0] = assemblyNameSymbol.ComponentRef; + assemblyNameRow[1] = assemblyNameSymbol.Name; + assemblyNameRow[2] = assemblyNameSymbol.Value; + + // assume value field has been modified + assemblyNameRow.Fields[2].Modified = true; + assemblyNameRow.Operation = fileRow.Operation; + } + } } } + private Dictionary> IndexFileFacadesByDiskId() + { + var fileFacadesByDiskId = new Dictionary>(); + + // Index patch file facades by diskId+fileId. + foreach (var facade in this.FileFacades) + { + if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) + { + mediaFacades = new Dictionary(); + fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); + } + + mediaFacades.Add(facade.Id, facade); + } + + return fileFacadesByDiskId; + } + /// /// Adds the PatchFiles action to the sequence table if it does not already exist. /// @@ -349,6 +332,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + private static bool TryModifyField(Row row, int index, object value) + { + var field = row.Fields[index]; + + if (field.Data != value) + { + field.Data = value; + field.Modified = true; + + if (row.Operation == RowOperation.None) + { + row.Operation = RowOperation.Modify; + } + } + + return field.Modified; + } + /// /// Tests sequence table for PatchFiles and associated actions /// @@ -396,33 +397,29 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - var componentKeyPath = new Dictionary(componentTable.Rows.Count); - // Index the Component table for non-directory & non-registry key paths. - foreach (var row in componentTable.Rows) + var componentKeyPath = new Dictionary(); + foreach (var row in componentTable.Rows.Cast().Where(r => !r.IsRegistryKeyPath)) { - var keyPath = row.FieldAsString(5); - if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + var keyPath = row.KeyPath; + + if (!String.IsNullOrEmpty(keyPath)) { - componentKeyPath.Add(row.FieldAsString(0), keyPath); + componentKeyPath.Add(row.Component, keyPath); } } var componentWithChangedKeyPath = new Dictionary(); var componentWithNonKeyPathChanged = new Dictionary(); + // Verify changes in the file table, now that file diffing has occurred - foreach (FileRow row in fileTable.Rows) + foreach (var row in fileTable.Rows.Cast().Where(r => r.Operation == RowOperation.Modify)) { - if (RowOperation.Modify != row.Operation) - { - continue; - } - - var fileId = row.FieldAsString(0); - var componentId = row.FieldAsString(1); + var fileId = row.File; + var componentId = row.Component; // If this file is the keypath of a component - if (componentKeyPath.ContainsValue(fileId)) + if (componentKeyPath.ContainsValue(componentId)) { if (!componentWithChangedKeyPath.ContainsKey(componentId)) { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs index 727e20b2..68cb6554 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs @@ -21,6 +21,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine { this.Messaging = serviceProvider.GetService(); this.BackendHelper = serviceProvider.GetService(); + this.PathResolver = serviceProvider.GetService(); this.ExtensionManager = serviceProvider.GetService(); } @@ -28,6 +29,8 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine private IBackendHelper BackendHelper { get; } + private IPathResolver PathResolver { get; } + private IExtensionManager ExtensionManager { get; } private string OutputPath { get; set; } @@ -40,8 +43,6 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine private string IntermediateFolder { get; set; } - private bool IsAdminImage { get; set; } - private bool PreserveUnchangedRows { get; set; } private bool ShowPedanticMessages { get; set; } @@ -133,10 +134,6 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine var parameter = argument.Substring(1); switch (parameter.ToLowerInvariant()) { - case "a": - this.IsAdminImage = true; - return true; - case "intermediatefolder": this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument); return true; @@ -194,15 +191,15 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine } } - case "val": + case "t": { - var val = parser.GetNextArgumentOrError(argument); - if (String.IsNullOrEmpty(val)) + var type = parser.GetNextArgumentOrError(argument); + if (String.IsNullOrEmpty(type)) { return true; } - switch (val.ToLowerInvariant()) + switch (type.ToLowerInvariant()) { case "language": this.ValidationFlags |= TransformFlags.LanguageTransformDefault; @@ -216,6 +213,22 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine this.ValidationFlags |= TransformFlags.PatchTransformDefault; return true; + default: + parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, type, new[] { "language", "instance", "patch" })); + return true; + } + } + + case "val": + { + var val = parser.GetNextArgumentOrError(argument); + if (String.IsNullOrEmpty(val)) + { + return true; + } + + switch (val.ToLowerInvariant()) + { case "g": this.ValidationFlags |= TransformFlags.ValidateUpgradeCode; return true; @@ -261,7 +274,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine return true; default: - parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, val, new[] { "language", "instance", "patch", "g", "l", "r", "s", "t", "u", "v", "w", "x", "y", "z" })); + parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, val, new[] { "g", "l", "r", "s", "t", "u", "v", "w", "x", "y", "z" })); return true; } } @@ -297,7 +310,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine { Exception exception; - (transform, exception) = LoadWindowsInstallerDataSafely(this.TargetPath); + (transform, exception) = DataLoader.LoadWindowsInstallerDataSafely(this.TargetPath); if (transform?.Type != OutputType.Transform) { @@ -340,17 +353,9 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine private WindowsInstallerData CreateTransform() { - if (!TryLoadWindowsInstallerData(this.TargetPath, out var targetOutput)) - { - var unbindCommand = new UnbindMsiOrMsmCommand(this.Messaging, this.BackendHelper, this.TargetPath, this.ExportBasePath, this.IntermediateFolder, this.IsAdminImage, suppressDemodularization: true, suppressExtractCabinets: true); - targetOutput = unbindCommand.Execute(); - } + var targetData = this.GetWindowsInstallerData(this.TargetPath); - if (!TryLoadWindowsInstallerData(this.TargetPath, out var updatedOutput)) - { - var unbindCommand = new UnbindMsiOrMsmCommand(this.Messaging, this.BackendHelper, this.UpdatedPath, this.ExportBasePath, this.IntermediateFolder, this.IsAdminImage, suppressDemodularization: true, suppressExtractCabinets: true); - updatedOutput = unbindCommand.Execute(); - } + var updatedData = this.GetWindowsInstallerData(this.UpdatedPath); var differ = new Differ(this.Messaging) { @@ -359,7 +364,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine SuppressKeepingSpecialRows = this.SuppressKeepingSpecialRows }; - return differ.Diff(targetOutput, updatedOutput, this.ValidationFlags); + return differ.Diff(targetData, updatedData, this.ValidationFlags); } private TableDefinitionCollection GetTableDefinitions() @@ -370,37 +375,15 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine return loadTableDefinitions.Execute(); } - private static bool TryLoadWindowsInstallerData(string path, out WindowsInstallerData data) + private WindowsInstallerData GetWindowsInstallerData(string path) { - data = null; - - var extension = Path.GetExtension(path); - - // If the path is _not_ obviously a Windows Installer database, let's try opening it as - // our own data file format. - if (!extension.Equals(".msi", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".msm", StringComparison.OrdinalIgnoreCase)) - { - (data, _) = LoadWindowsInstallerDataSafely(path); - } - - return data != null; - } - - private static (WindowsInstallerData, Exception) LoadWindowsInstallerDataSafely(string path) - { - WindowsInstallerData data = null; - Exception exception = null; - - try - { - data = WindowsInstallerData.Load(path); - } - catch (Exception e) + if (!DataLoader.TryLoadWindowsInstallerData(path, out var data)) { - exception = e; + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, OutputType.Product, this.ExportBasePath, null, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false); + data = unbindCommand.Execute(); } - return (data, exception); + return data; } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs index ae9492eb..f4e4a1fc 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs @@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; using WixToolset.Extensibility.Services; /// @@ -18,7 +17,7 @@ namespace WixToolset.Core.WindowsInstaller /// public sealed class Differ { - private const char sectionDelimiter = '/'; + private const char SectionDelimiter = '/'; private readonly IMessaging messaging; private SummaryInformationStreams transformSummaryInfo; @@ -48,17 +47,6 @@ namespace WixToolset.Core.WindowsInstaller /// The option to keep all rows including unchanged rows. public bool PreserveUnchangedRows { get; set; } - /// - /// Creates a transform by diffing two outputs. - /// - /// The target output. - /// The updated output. - /// The transform. - public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput) - { - return this.Diff(targetOutput, updatedOutput, 0); - } - /// /// Creates a transform by diffing two outputs. /// @@ -68,9 +56,12 @@ namespace WixToolset.Core.WindowsInstaller /// The transform. public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags) { - var transform = new WindowsInstallerData(null); - transform.Type = OutputType.Transform; - transform.Codepage = updatedOutput.Codepage; + var transform = new WindowsInstallerData(null) + { + Type = OutputType.Transform, + Codepage = updatedOutput.Codepage + }; + this.transformSummaryInfo = new SummaryInformationStreams(); // compare the codepages @@ -120,7 +111,7 @@ namespace WixToolset.Core.WindowsInstaller foreach (var updatedRow in updatedTable.Rows) { updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId; addedTable.Rows.Add(updatedRow); } } @@ -209,7 +200,7 @@ namespace WixToolset.Core.WindowsInstaller else if (null == updatedRow) { operation = targetRow.Operation = RowOperation.Delete; - targetRow.SectionId += sectionDelimiter; + targetRow.SectionId += SectionDelimiter; comparedRow = targetRow; keepRow = true; } @@ -222,7 +213,7 @@ namespace WixToolset.Core.WindowsInstaller // ignore rows that shouldn't be in a transform if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) { - updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + updatedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId; comparedRow = updatedRow; keepRow = true; operation = RowOperation.Modify; @@ -306,7 +297,7 @@ namespace WixToolset.Core.WindowsInstaller if (keepRow) { comparedRow = updatedRow; - comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + comparedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId; } } } @@ -350,8 +341,9 @@ namespace WixToolset.Core.WindowsInstaller // diff the target and updated rows foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) { - var targetPrimaryKey = targetPrimaryKeyEntry.Key; - var compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value, updatedPrimaryKeys[targetPrimaryKey], out var _, out var keepRow); + updatedPrimaryKeys.TryGetValue(targetPrimaryKeyEntry.Key, out var updatedRow); + + var compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value, updatedRow, out var _, out var keepRow); if (keepRow) { @@ -369,7 +361,7 @@ namespace WixToolset.Core.WindowsInstaller var updatedRow = (Row)updatedPrimaryKeyEntry.Value; updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId; rows.Add(updatedRow); } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs new file mode 100644 index 00000000..eede6e24 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs @@ -0,0 +1,82 @@ +// 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.ExtensibilityServices +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + + internal class FileFacade : IFileFacade + { + public FileFacade(FileSymbol file) + { + this.Identifier = file.Id; + this.ComponentRef = file.ComponentRef; + this.DiskId = file.DiskId ?? 1; + this.FileName = file.Name; + this.FileSize = file.FileSize; + this.Language = file.Language; + this.PatchGroup = file.PatchGroup; + this.Sequence = file.Sequence; + this.SourceLineNumber = file.SourceLineNumbers; + this.SourcePath = file.Source?.Path; + this.Compressed = (file.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed; + this.Uncompressed = (file.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed; + this.Version = file.Version; + this.AssemblyNameSymbols = new List(); + } + + public FileFacade(FileRow row) + { + this.Identifier = new Identifier(AccessModifier.Section, row.File); + this.ComponentRef = row.Component; + this.DiskId = row.DiskId; + this.FileName = row.FileName; + this.FileSize = row.FileSize; + this.Language = row.Language; + this.PatchGroup = null; + this.Sequence = row.Sequence; + this.SourceLineNumber = row.SourceLineNumbers; + this.SourcePath = row.Source; + this.Compressed = (row.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; + this.Uncompressed = (row.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + this.Version = row.Version; + this.AssemblyNameSymbols = new List(); + } + + public string Id => this.Identifier.Id; + + public Identifier Identifier { get; } + + public string ComponentRef { get; } + + public int DiskId { get; set; } + + public string FileName { get; } + + public int FileSize { get; set; } + + public string Language { get; set; } + + public int? PatchGroup { get; } + + public int Sequence { get; set; } + + public SourceLineNumber SourceLineNumber { get; } + + public string SourcePath { get; } + + public bool Compressed { get; } + + public bool Uncompressed { get; } + + public string Version { get; set; } + + public MsiFileHashSymbol MsiFileHashSymbol { get; set; } + + public ICollection AssemblyNameSymbols { get; } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs index ad738321..f372af82 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs @@ -23,19 +23,14 @@ namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices #region IBackendHelper interfaces - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) + public IFileFacade CreateFileFacade(FileSymbol file) { - return this.backendHelper.CreateFileFacade(file, assembly); + return new FileFacade(file); } public IFileFacade CreateFileFacade(FileRow fileRow) { - return this.backendHelper.CreateFileFacade(fileRow); - } - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) - { - return this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + return new FileFacade(fileRow); } public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs index f73791aa..27bb282b 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -2,11 +2,7 @@ namespace WixToolset.Core.WindowsInstaller { - using System; using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Core.WindowsInstaller.Decompile; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs index 38a4ab34..bccdd3d4 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs @@ -2,7 +2,6 @@ namespace WixToolset.Core.WindowsInstaller { - using System; using System.Collections.Generic; using WixToolset.Core.WindowsInstaller.Bind; using WixToolset.Data.WindowsInstaller; @@ -18,8 +17,14 @@ namespace WixToolset.Core.WindowsInstaller var backendHelper = context.ServiceProvider.GetService(); + var pathResolver = context.ServiceProvider.GetService(); + + var fileResolver = context.ServiceProvider.GetService(); + var extensionManager = context.ServiceProvider.GetService(); + var resolveExtensions = extensionManager.GetServices(); + var backendExtensions = extensionManager.GetServices(); foreach (var extension in backendExtensions) @@ -30,7 +35,7 @@ namespace WixToolset.Core.WindowsInstaller // Create transforms named in patch transforms. IEnumerable patchTransforms; { - var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder); + var command = new CreatePatchTransformsCommand(messaging, backendHelper, pathResolver, fileResolver, resolveExtensions, context.IntermediateRepresentation, context.IntermediateFolder, context.BindPaths); patchTransforms = command.Execute(); } @@ -42,7 +47,7 @@ namespace WixToolset.Core.WindowsInstaller } // Create WindowsInstallerData with patch metdata and transforms as sub-storages - // Create MSP from WindowsInstallerData + // and create MSP from that WindowsInstallerData. IBindResult result = null; try { @@ -62,90 +67,5 @@ namespace WixToolset.Core.WindowsInstaller throw; } } - -#if TODO_PATCHING - public Intermediate Unbind(IUnbindContext context) - { - Output patch; - - // patch files are essentially database files (use a special flag to let the API know its a patch file) - try - { - using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false); - patch = unbindCommand.Execute(); - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath)); - } - - throw; - } - - // retrieve the transforms (they are in substorages) - using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite)) - { - Table summaryInformationTable = patch.Tables["_SummaryInformation"]; - foreach (Row row in summaryInformationTable.Rows) - { - if (8 == (int)row[0]) // PID_LASTAUTHOR - { - string value = (string)row[1]; - - foreach (string decoratedSubStorageName in value.Split(';')) - { - string subStorageName = decoratedSubStorageName.Substring(1); - string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); - - // ensure the parent directory exists - Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); - - // copy the substorage to a new storage for the transform file - using (Storage subStorage = storage.OpenStorage(subStorageName)) - { - using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create)) - { - subStorage.CopyTo(transformStorage); - } - } - - // unbind the transform - var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder); - var transform = unbindCommand.Execute(); - - patch.SubStorages.Add(new SubStorage(subStorageName, transform)); - } - - break; - } - } - } - - // extract the files from the cabinets - // TODO: use per-transform export paths for support of multi-product patches - if (null != context.ExportBasePath && !context.SuppressExtractCabinets) - { - using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - foreach (SubStorage subStorage in patch.SubStorages) - { - // only patch transforms should carry files - if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) - { - var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder); - extractCommand.Execute(); - } - } - } - } - - return patch; - } -#endif } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs index 8f52bed9..9aa6f3d4 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs @@ -3,7 +3,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind { using System; - using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -26,7 +25,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind this.TreatOutputAsModule = treatOutputAsModule; } - public string[] ExtractedFiles { get; private set; } + public Dictionary ExtractedFileIdsWithMediaRow { get; private set; } private WindowsInstallerData Output { get; } @@ -42,47 +41,51 @@ namespace WixToolset.Core.WindowsInstaller.Unbind public void Execute() { + var extractedFileIdsWithMediaRow = new Dictionary(); var databaseBasePath = Path.GetDirectoryName(this.InputFilePath); - var cabinetFiles = new List(); - var embeddedCabinets = new SortedList(); + + var cabinetPathsWithMediaRow = new Dictionary(); + var embeddedCabinetNamesByDiskId = new SortedDictionary(); + var embeddedCabinetRowsByDiskId = new SortedDictionary(); // index all of the cabinet files if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule) { - embeddedCabinets.Add(0, "MergeModule.CABinet"); + embeddedCabinetNamesByDiskId.Add(0, "MergeModule.CABinet"); } - else if (null != this.Output.Tables["Media"]) + else if (this.Output.Tables.TryGetTable("Media", out var mediaTable)) { - foreach (MediaRow mediaRow in this.Output.Tables["Media"].Rows) + foreach (var mediaRow in mediaTable.Rows.Cast().Where(r => !String.IsNullOrEmpty(r.Cabinet))) { - if (null != mediaRow.Cabinet) + if (OutputType.Product == this.Output.Type || + (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation)) { - if (OutputType.Product == this.Output.Type || - (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation)) + if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) { - if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) - { - embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); - } - else - { - cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet)); - } + embeddedCabinetNamesByDiskId.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); + embeddedCabinetRowsByDiskId.Add(mediaRow.DiskId, mediaRow); + } + else + { + cabinetPathsWithMediaRow.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet), mediaRow); } } } } - // extract the embedded cabinet files from the database - if (0 < embeddedCabinets.Count) + // Extract any embedded cabinet files from the database. + if (0 < embeddedCabinetRowsByDiskId.Count) { using (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) { - foreach (int diskId in embeddedCabinets.Keys) + foreach (var diskIdWithCabinetName in embeddedCabinetNamesByDiskId) { + var diskId = diskIdWithCabinetName.Key; + var cabinetName = diskIdWithCabinetName.Value; + using (var record = new Record(1)) { - record.SetString(1, (string)embeddedCabinets[diskId]); + record.SetString(1, cabinetName); streamsView.Execute(record); } @@ -92,15 +95,15 @@ namespace WixToolset.Core.WindowsInstaller.Unbind { // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive, // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work - var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); + var cabinetPath = Path.Combine(this.IntermediateFolder, "Media", diskId.ToString(CultureInfo.InvariantCulture), ".cab"); // ensure the parent directory exists - Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); + Directory.CreateDirectory(Path.GetDirectoryName(cabinetPath)); - using (var fs = File.Create(cabinetFile)) + using (var fs = File.Create(cabinetPath)) { int bytesRead; - var buffer = new byte[512]; + var buffer = new byte[4096]; while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) { @@ -108,7 +111,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } } - cabinetFiles.Add(cabinetFile); + embeddedCabinetRowsByDiskId.TryGetValue(diskId, out var cabinetMediaRow); + cabinetPathsWithMediaRow.Add(cabinetPath, cabinetMediaRow); } else { @@ -119,29 +123,34 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } } - // extract the cabinet files - if (0 < cabinetFiles.Count) + // Extract files from any available cabinets. + if (0 < cabinetPathsWithMediaRow.Count) { - // ensure the directory exists or extraction will fail Directory.CreateDirectory(this.ExportBasePath); - foreach (var cabinetFile in cabinetFiles) + foreach (var cabinetPathWithMediaRow in cabinetPathsWithMediaRow) { + var cabinetPath = cabinetPathWithMediaRow.Key; + var cabinetMediaRow = cabinetPathWithMediaRow.Value; + try { - var cabinet = new Cabinet(cabinetFile); - this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray(); + var cabinet = new Cabinet(cabinetPath); + var cabinetFilesExtracted = cabinet.Extract(this.ExportBasePath); + + foreach (var extractedFile in cabinetFilesExtracted) + { + extractedFileIdsWithMediaRow.Add(extractedFile, cabinetMediaRow); + } } catch (FileNotFoundException) { - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); + throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetPath)); } } } - else - { - this.ExtractedFiles = new string[0]; - } + + this.ExtractedFileIdsWithMediaRow = extractedFileIdsWithMediaRow; } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs index c1fb7f12..7bbbbd76 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs @@ -5,29 +5,35 @@ namespace WixToolset.Core.WindowsInstaller.Unbind using System; using System.Collections; using System.Collections.Generic; + using System.ComponentModel; using System.Globalization; using System.IO; + using System.Linq; using System.Text.RegularExpressions; using WixToolset.Core.Native.Msi; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; internal class UnbindDatabaseCommand { - private List exportedFiles; + private static readonly Regex Modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); - public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) + private int sectionCount; + + public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, string databasePath, OutputType outputType, string exportBasePath, string extractFilesFolder, string intermediateFolder, bool enableDemodularization, bool skipSummaryInfo) { this.Messaging = messaging; this.BackendHelper = backendHelper; - this.Database = database; + this.PathResolver = pathResolver; this.DatabasePath = databasePath; this.OutputType = outputType; this.ExportBasePath = exportBasePath; + this.ExtractFilesFolder = extractFilesFolder; this.IntermediateFolder = intermediateFolder; - this.IsAdminImage = isAdminImage; - this.SuppressDemodularization = suppressDemodularization; + this.EnableDemodularization = enableDemodularization; this.SkipSummaryInfo = skipSummaryInfo; this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); @@ -37,7 +43,9 @@ namespace WixToolset.Core.WindowsInstaller.Unbind public IBackendHelper BackendHelper { get; } - public Database Database { get; } + private IPathResolver PathResolver { get; } + + private Database Database { get; set; } public string DatabasePath { get; } @@ -45,73 +53,91 @@ namespace WixToolset.Core.WindowsInstaller.Unbind public string ExportBasePath { get; } - public string IntermediateFolder { get; } + public string ExtractFilesFolder { get; } - public bool IsAdminImage { get; } + public string IntermediateFolder { get; } - public bool SuppressDemodularization { get; } + public bool EnableDemodularization { get; } public bool SkipSummaryInfo { get; } public TableDefinitionCollection TableDefinitions { get; } - public IEnumerable ExportedFiles => this.exportedFiles; + public bool AdminImage { get; private set; } - private int SectionCount { get; set; } + public IEnumerable ExportedFiles { get; private set; } public WindowsInstallerData Execute() { - this.exportedFiles = new List(); + var adminImage = false; + var exportedFiles = new List(); - string modularizationGuid = null; - var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)); - View validationView = null; + var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)) + { + Type = this.OutputType + }; + + try + { + using (var database = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) + { + this.Database = database; - // set the output type - output.Type = this.OutputType; + Directory.CreateDirectory(this.IntermediateFolder); - Directory.CreateDirectory(this.IntermediateFolder); + output.Codepage = this.GetCodePage(); - // get the codepage - this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); - using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) - { - string line; + var modularizationGuid = this.ProcessTables(output, exportedFiles); - while (null != (line = sr.ReadLine())) - { - var data = line.Split('\t'); + var summaryInfo = this.ProcessSummaryInfo(output, modularizationGuid); - if (2 == data.Length) - { - output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); - } + this.UpdateUnrealFileColumns(this.DatabasePath, output, summaryInfo, exportedFiles); + + this.GenerateSectionIds(output); } } - - // get the summary information table if it exists; it won't if unbinding a transform - if (!this.SkipSummaryInfo) + catch (Win32Exception e) { - using (var summaryInformation = new SummaryInformation(this.Database)) + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED { - var table = new Table(this.TableDefinitions["_SummaryInformation"]); + throw new WixException(ErrorMessages.OpenDatabaseFailed(this.DatabasePath)); + } - for (var i = 1; 19 >= i; i++) - { - var value = summaryInformation.GetProperty(i); + throw; + } - if (0 < value.Length) - { - var row = table.CreateRow(output.SourceLineNumbers); - row[0] = i; - row[1] = value; - } - } + this.AdminImage = adminImage; + this.ExportedFiles = exportedFiles; - output.Tables.Add(table); + return output; + } + + private int GetCodePage() + { + var codepage = 0; + + this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); + + var lines = File.ReadAllLines(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt")); + + if (lines.Length == 3) + { + var data = lines[2].Split('\t'); + + if (2 == data.Length) + { + codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); } } + return codepage; + } + + private string ProcessTables(WindowsInstallerData output, List exportedFiles) + { + View validationView = null; + string modularizationGuid = null; + try { // open a view on the validation table if it exists @@ -127,7 +153,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind { var tableName = tableRecord.GetString(1); - using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) + using (var tableView = this.Database.OpenExecuteView($"SELECT * FROM `{tableName}`")) { var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); var table = new Table(tableDefinition); @@ -154,16 +180,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind switch (row.Fields[i].Column.Type) { case ColumnType.Number: - var success = false; var intValue = rowRecord.GetInteger(i + 1); - if (row.Fields[i].Column.IsLocalizable) - { - success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); - } - else - { - success = row.BestEffortSetField(i, intValue); - } + var success = row.Fields[i].Column.IsLocalizable ? row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)) : row.BestEffortSetField(i, intValue); if (!success) { @@ -171,20 +189,18 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } break; case ColumnType.Object: - var sourceFile = "FILE NOT EXPORTED"; + var source = "FILE NOT EXPORTED"; if (null != this.ExportBasePath) { - var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); - sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile); + source = Path.Combine(this.ExportBasePath, tableName, row.GetPrimaryKey('.')); - // ensure the parent directory exists - System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); + Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); - using (var fs = System.IO.File.Create(sourceFile)) + using (var fs = File.Create(source)) { int bytesRead; - var buffer = new byte[512]; + var buffer = new byte[4096]; while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) { @@ -192,10 +208,10 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } } - this.exportedFiles.Add(sourceFile); + exportedFiles.Add(source); } - row[i] = sourceFile; + row[i] = source; break; default: var value = rowRecord.GetString(i + 1); @@ -203,27 +219,26 @@ namespace WixToolset.Core.WindowsInstaller.Unbind switch (row.Fields[i].Column.Category) { case ColumnCategory.Guid: - value = value.ToUpper(CultureInfo.InvariantCulture); + value = value.ToUpperInvariant(); break; } - // de-modularize - if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) + // De-modularize + if (this.EnableDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) { - var modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); - if (null == modularizationGuid) { - var match = modularization.Match(value); + var match = Modularization.Match(value); if (match.Success) { modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); } } - value = modularization.Replace(value, String.Empty); + value = Modularization.Replace(value, String.Empty); } +#if TODO_MOVE_TO_DECOMPILER // escape "$(" for the preprocessor value = value.Replace("$(", "$$("); @@ -234,6 +249,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind //{ // value = value.Insert(matches[j].Index, "!"); //} +#endif row[i] = value; break; @@ -249,33 +265,54 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } finally { - if (null != validationView) - { - validationView.Close(); - } + validationView?.Close(); } - // set the modularization guid as the PackageCode - if (null != modularizationGuid) - { - var table = output.Tables["_SummaryInformation"]; + return modularizationGuid; + } - foreach (var row in table.Rows) + private SummaryInformationBits ProcessSummaryInfo(WindowsInstallerData output, string modularizationGuid) + { + var result = new SummaryInformationBits(); + + if (!this.SkipSummaryInfo) + { + using (var summaryInformation = new SummaryInformation(this.Database)) { - if (9 == (int)row[0]) // PID_REVNUMBER + var table = new Table(this.TableDefinitions["_SummaryInformation"]); + + for (var i = 1; 19 >= i; i++) { - row[1] = modularizationGuid; + var value = summaryInformation.GetProperty(i); + + // Set the modularization guid as the PackageCode, for merge modules. + if (i == (int)SummaryInformation.Package.PackageCode && !String.IsNullOrEmpty(modularizationGuid)) + { + var row = table.CreateRow(output.SourceLineNumbers); + row[0] = i; + row[1] = modularizationGuid; + } + else if (0 < value.Length) + { + var row = table.CreateRow(output.SourceLineNumbers); + row[0] = i; + row[1] = value; + + if (i == (int)SummaryInformation.Package.FileAndElevatedFlags) + { + var wordcount = Convert.ToInt32(value, CultureInfo.InvariantCulture); + result.LongFilenames = (wordcount & 0x1) != 0x1; + result.Compressed = (wordcount & 0x2) == 0x2; + result.AdminImage = (wordcount & 0x4) == 0x4; + } + } } - } - } - if (this.IsAdminImage) - { - this.GenerateWixFileTable(this.DatabasePath, output); - this.GenerateSectionIds(output); + output.Tables.Add(table); + } } - return output; + return result; } private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) @@ -310,10 +347,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind var columnName = columnNameRecord.GetString(i); var idtType = columnTypeRecord.GetString(i); - ColumnType columnType; - int length; - bool nullable; - var columnCategory = ColumnCategory.Unknown; var columnModularizeType = ColumnModularizeType.None; var primary = tablePrimaryKeys.Contains(columnName); @@ -326,7 +359,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind string description = null; // get the column type, length, and whether its nullable - switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) + ColumnType columnType; + switch (Char.ToLowerInvariant(idtType[0])) { case 'i': columnType = ColumnType.Number; @@ -345,8 +379,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind columnType = ColumnType.Unknown; break; } - length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); - nullable = Char.IsUpper(idtType[0]); + var length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); + var nullable = Char.IsUpper(idtType[0]); // try to get validation information if (null != validationView) @@ -385,11 +419,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind // convert category to ColumnCategory if (null != category) { - try - { - columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); - } - catch (ArgumentException) + if (!Enum.TryParse(category, true, out columnCategory)) { columnCategory = ColumnCategory.Unknown; } @@ -427,67 +457,103 @@ namespace WixToolset.Core.WindowsInstaller.Unbind return new TableDefinition(tableName, null, columns, false); } - /// - /// Generates the WixFile table based on a path to an admin image msi and an Output. - /// - /// The path to the msi database file in an admin image. - /// The Output that represents the msi database. - private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output) + private void UpdateUnrealFileColumns(string databaseFile, WindowsInstallerData output, SummaryInformationBits summaryInformation, List exportedFiles) { - throw new NotImplementedException(); -#if TODO_FIX_UNBINDING_FILES - var adminRootPath = Path.GetDirectoryName(databaseFile); + var fileRows = output.Tables["File"]?.Rows; - var componentDirectoryIndex = new Hashtable(); - var componentTable = output.Tables["Component"]; - foreach (var row in componentTable.Rows) + if (fileRows == null || fileRows.Count == 0) { - componentDirectoryIndex.Add(row[0], row[2]); + return; } + this.UpdateFileRowsDiskId(output, fileRows); + + this.UpdateFileRowsSource(databaseFile, output, fileRows, summaryInformation, exportedFiles); + } + + private void UpdateFileRowsDiskId(WindowsInstallerData output, IList fileRows) + { + var mediaRows = output.Tables["Media"]?.Rows?.Cast()?.OrderBy(r => r.LastSequence)?.ToList(); + + var lastMediaRowIndex = 0; + var lastMediaRow = (mediaRows == null || mediaRows.Count == 0) ? null : mediaRows[lastMediaRowIndex]; + + foreach (var fileRow in fileRows.Cast()?.OrderBy(r => r.Sequence)) + { + while (lastMediaRow != null && fileRow.Sequence > lastMediaRow.LastSequence) + { + ++lastMediaRowIndex; + + lastMediaRow = lastMediaRowIndex < mediaRows.Count ? mediaRows[lastMediaRowIndex] : null; + } + + fileRow.DiskId = lastMediaRow?.DiskId ?? 1; + } + } + + private void UpdateFileRowsSource(string databasePath, WindowsInstallerData output, IList fileRows, SummaryInformationBits summaryInformation, List exportedFiles) + { + var databaseFolder = Path.GetDirectoryName(databasePath); + + var componentDirectoryIndex = output.Tables["Component"].Rows.Cast().ToDictionary(r => r.Component, r => r.Directory); + // Index full source paths for all directories - var directoryDirectoryParentIndex = new Hashtable(); - var directoryFullPathIndex = new Hashtable(); - var directorySourceNameIndex = new Hashtable(); + var directories = new Dictionary(); + var directoryTable = output.Tables["Directory"]; foreach (var row in directoryTable.Rows) { - directoryDirectoryParentIndex.Add(row[0], row[1]); - if (null == row[1]) - { - directoryFullPathIndex.Add(row[0], adminRootPath); - } - else - { - directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); - } + var sourceName = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), source: true, longName: summaryInformation.LongFilenames); + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(row.FieldAsString(1), sourceName); + + directories.Add(row.FieldAsString(0), resolvedDirectory); } - foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) + if (summaryInformation.AdminImage) { - if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) + foreach (var fileRow in fileRows.Cast()) { - this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); + var directoryId = componentDirectoryIndex[fileRow.Component]; + var relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, directoryId, fileRow.FileName, compressed: false, useLongName: summaryInformation.LongFilenames); + + fileRow.Source = Path.Combine(databaseFolder, relativeFileLayoutPath); } } - - var fileTable = output.Tables["File"]; - var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); - foreach (var row in fileTable.Rows) + else { - var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); - wixFileRow.File = (string)row[0]; - wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; - wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); + var extractedFileIds = new HashSet(); - if (!File.Exists(wixFileRow.Source)) + if (!String.IsNullOrEmpty(this.ExtractFilesFolder)) { - throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source)); + var extractCommand = new ExtractCabinetsCommand(output, this.Database, this.DatabasePath, this.ExtractFilesFolder, this.IntermediateFolder); + extractCommand.Execute(); + + extractedFileIds = new HashSet(extractCommand.ExtractedFileIdsWithMediaRow.Keys, StringComparer.OrdinalIgnoreCase); + exportedFiles.AddRange(extractedFileIds); } - wixFileTable.Rows.Add(wixFileRow); + foreach (var fileRow in fileRows.Cast()) + { + var source = "FILE NOT EXPORTED"; + + if (fileRow.Compressed == YesNoType.Yes || (fileRow.Compressed == YesNoType.NotSet && summaryInformation.Compressed)) + { + if (extractedFileIds.Contains(fileRow.File)) + { + source = Path.Combine(this.ExtractFilesFolder, fileRow.File); + } + } + else + { + var directoryId = componentDirectoryIndex[fileRow.Component]; + var relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, directoryId, fileRow.FileName, compressed: false, useLongName: summaryInformation.LongFilenames); + + source = Path.Combine(databaseFolder, relativeFileLayoutPath); + } + + fileRow.Source = source; + } } -#endif } /// @@ -620,7 +686,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind { switch (table.Name) { - case "WixFile": case "MsiFileHash": ConnectTableToSection(table, fileSectionIdIndex, 0); break; @@ -699,7 +764,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } } - // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. + // Now pass the output to each unbinder extension to allow them to analyze the output and determine their proper section ids. //foreach (IUnbinderExtension extension in this.unbinderExtensions) //{ // extension.GenerateSectionIds(output); @@ -711,19 +776,22 @@ namespace WixToolset.Core.WindowsInstaller.Unbind /// /// The table to add sections to. /// The index of the column which is used by other tables to reference this table. - /// A Hashtable containing the tables key for each row paired with its assigned section id. - private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) + /// A dictionary containing the tables key for each row paired with its assigned section id. + private Dictionary AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) { - var hashtable = new Hashtable(); + var primaryKeyToSectionId = new Dictionary(); + if (null != table) { foreach (var row in table.Rows) { row.SectionId = this.GetNewSectionId(); - hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); + + primaryKeyToSectionId.Add(row.FieldAsString(rowPrimaryKeyIndex), row.SectionId); } } - return hashtable; + + return primaryKeyToSectionId; } /// @@ -732,15 +800,15 @@ namespace WixToolset.Core.WindowsInstaller.Unbind /// The table containing rows that need to be connected to sections. /// A hashtable containing keys to map table to its section. /// The index of the column which is used as the foreign key in to the sectionIdIndex. - private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex) + private static void ConnectTableToSection(Table table, Dictionary sectionIdIndex, int rowIndex) { if (null != table) { foreach (var row in table.Rows) { - if (sectionIdIndex.ContainsKey(row[rowIndex])) + if (sectionIdIndex.TryGetValue(row.FieldAsString(rowIndex), out var sectionId)) { - row.SectionId = (string)sectionIdIndex[row[rowIndex]]; + row.SectionId = sectionId; } } } @@ -750,40 +818,53 @@ namespace WixToolset.Core.WindowsInstaller.Unbind /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it. /// /// The table containing rows that need to be connected to sections. - /// A hashtable containing keys to map table to its section. + /// A dictionary containing keys to map table to its section. /// The index of the column which is used as the foreign key in to the sectionIdIndex. /// The index of the column which is used by other tables to reference this table. - /// A Hashtable containing the tables key for each row paired with its assigned section id. - private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) + /// A dictionary containing the tables key for each row paired with its assigned section id. + private static Dictionary ConnectTableToSectionAndIndex(Table table, Dictionary sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) { - var newHashTable = new Hashtable(); + var newPrimaryKeyToSectionId = new Dictionary(); + if (null != table) { foreach (var row in table.Rows) { - if (!sectionIdIndex.ContainsKey(row[rowIndex])) + var foreignKey = row.FieldAsString(rowIndex); + + if (!sectionIdIndex.TryGetValue(foreignKey, out var sectionId)) { continue; } - row.SectionId = (string)sectionIdIndex[row[rowIndex]]; - if (null != row[rowPrimaryKeyIndex]) + row.SectionId = sectionId; + + var primaryKey = row.FieldAsString(rowPrimaryKeyIndex); + + if (!String.IsNullOrEmpty(primaryKey) && sectionIdIndex.ContainsKey(primaryKey)) { - newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); + newPrimaryKeyToSectionId.Add(primaryKey, row.SectionId); } } } - return newHashTable; + + return newPrimaryKeyToSectionId; } - /// - /// Creates a new section identifier to be used when adding a section to an output. - /// - /// A string representing a new section id. private string GetNewSectionId() { - this.SectionCount++; - return "wix.section." + this.SectionCount.ToString(CultureInfo.InvariantCulture); + this.sectionCount++; + + return "wix.section." + this.sectionCount.ToString(CultureInfo.InvariantCulture); + } + + private class SummaryInformationBits + { + public bool AdminImage { get; set; } + + public bool Compressed { get; set; } + + public bool LongFilenames { get; set; } } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs deleted file mode 100644 index 8070d42d..00000000 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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.Unbind -{ - using System; - using System.ComponentModel; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class UnbindMsiOrMsmCommand - { - public UnbindMsiOrMsmCommand(IMessaging messaging, IBackendHelper backendHelper, string databasePath, string exportBasePath, string intermediateFolder, bool adminImage, bool suppressDemodularization, bool suppressExtractCabinets) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.DatabasePath = databasePath; - this.ExportBasePath = exportBasePath; - this.IntermediateFolder = intermediateFolder; - this.IsAdminImage = adminImage; - this.SuppressDemodularization = suppressDemodularization; - this.SuppressExtractCabinets = suppressExtractCabinets; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private string DatabasePath { get; } - - private string ExportBasePath { get; } - - private string IntermediateFolder { get; } - - private bool IsAdminImage { get; } - - private bool SuppressDemodularization { get; } - - private bool SuppressExtractCabinets { get; } - - public WindowsInstallerData Execute() - { - try - { - using (var database = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) - { - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, this.DatabasePath, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, this.IsAdminImage, this.SuppressDemodularization, skipSummaryInfo: false); - var data = unbindCommand.Execute(); - - // extract the files from the cabinets - if (!String.IsNullOrEmpty(this.ExportBasePath) && !this.SuppressExtractCabinets) - { - var extractCommand = new ExtractCabinetsCommand(data, database, this.DatabasePath, this.ExportBasePath, this.IntermediateFolder); - extractCommand.Execute(); - } - - return data; - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - //throw new WixException(WixErrors.OpenDatabaseFailed(this.DatabasePath)); - } - - throw; - } - } - } -} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs index 01ff1a80..bce60e40 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs @@ -1,5 +1,7 @@ // 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. +#if TODO_KEEP_FOR_PATCH_UNBINDING_CONSIDERATION_IN_FUTURE + namespace WixToolset.Core.WindowsInstaller.Unbind { using System; @@ -17,10 +19,11 @@ namespace WixToolset.Core.WindowsInstaller.Unbind internal class UnbindTransformCommand { - public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder) + public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string transformFile, string exportBasePath, string intermediateFolder) { this.Messaging = messaging; this.BackendHelper = backendHelper; + this.FileSystemManager = fileSystemManager; this.TransformFile = transformFile; this.ExportBasePath = exportBasePath; this.IntermediateFolder = intermediateFolder; @@ -32,6 +35,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind private IBackendHelper BackendHelper { get; } + private FileSystemManager FileSystemManager { get; } + private string TransformFile { get; } private string ExportBasePath { get; } @@ -90,7 +95,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); // unbind the database - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true); var transformViewOutput = unbindCommand.Execute(); // index the added and possibly modified rows (added rows may also appears as modified rows) @@ -160,7 +165,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } // unbind the database - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true); var output = unbindCommand.Execute(); // index all the rows to easily find modified rows @@ -302,8 +307,9 @@ namespace WixToolset.Core.WindowsInstaller.Unbind private void GenerateDatabase(WindowsInstallerData output, string databaseFile) { - var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); command.Execute(); } } } +#endif diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs index b35e3587..c78ea93a 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs @@ -3,11 +3,9 @@ namespace WixToolset.Core.WindowsInstaller { using System; - using System.Collections.Generic; - using System.ComponentModel; + using System.ComponentModel.Design; using System.IO; using System.Linq; - using WixToolset.Core.Native.Msi; using WixToolset.Core.WindowsInstaller.Decompile; using WixToolset.Core.WindowsInstaller.Unbind; using WixToolset.Data; @@ -63,56 +61,32 @@ namespace WixToolset.Core.WindowsInstaller private IWindowsInstallerDecompileResult Execute(IWindowsInstallerDecompileContext context) { - var result = context.ServiceProvider.GetService(); - - try + // Delete the directory and its files to prevent cab extraction failure due to an existing file. + if (Directory.Exists(context.ExtractFolder)) { - using (var database = new Database(context.DecompilePath, OpenDatabase.ReadOnly)) - { - // Delete the directory and its files to prevent cab extraction failure due to an existing file. - if (Directory.Exists(context.ExtractFolder)) - { - Directory.Delete(context.ExtractFolder, true); - } - - var backendHelper = context.ServiceProvider.GetService(); - var decompilerHelper = context.ServiceProvider.GetService(); - - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, database, context.DecompilePath, context.DecompileType, context.ExtractFolder, context.IntermediateFolder, context.IsAdminImage, suppressDemodularization: false, skipSummaryInfo: false); - var output = unbindCommand.Execute(); - var extractedFilePaths = new List(unbindCommand.ExportedFiles); - - var decompiler = new Decompiler(this.Messaging, backendHelper, decompilerHelper, context.Extensions, context.ExtensionData, context.SymbolDefinitionCreator, context.BaseSourcePath, context.SuppressCustomTables, context.SuppressDroppingEmptyTables, context.SuppressRelativeActionSequencing, context.SuppressUI, context.TreatProductAsModule); - result.Document = decompiler.Decompile(output); - - result.Platform = GetPlatformFromOutput(output); - - // extract the files from the cabinets - if (!String.IsNullOrEmpty(context.ExtractFolder) && !context.SuppressExtractCabinets) - { - var fileDirectory = String.IsNullOrEmpty(context.CabinetExtractFolder) ? Path.Combine(context.ExtractFolder, "File") : context.CabinetExtractFolder; - - var extractCommand = new ExtractCabinetsCommand(output, database, context.DecompilePath, fileDirectory, context.IntermediateFolder, context.TreatProductAsModule); - extractCommand.Execute(); - - extractedFilePaths.AddRange(extractCommand.ExtractedFiles); - result.ExtractedFilePaths = extractedFilePaths; - } - else - { - result.ExtractedFilePaths = new string[0]; - } - } + Directory.Delete(context.ExtractFolder, true); } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(ErrorMessages.OpenDatabaseFailed(context.DecompilePath)); - } - throw; - } + var backendHelper = context.ServiceProvider.GetService(); + + var pathResolver = context.ServiceProvider.GetService(); + + var extractFilesFolder = context.SuppressExtractCabinets || (String.IsNullOrEmpty(context.CabinetExtractFolder) && String.IsNullOrEmpty(context.ExtractFolder)) ? null : + String.IsNullOrEmpty(context.CabinetExtractFolder) ? Path.Combine(context.ExtractFolder, "File") : context.CabinetExtractFolder; + + var outputType = context.TreatProductAsModule ? OutputType.Module : context.DecompileType; + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, pathResolver, context.DecompilePath, outputType, context.ExtractFolder, extractFilesFolder, context.IntermediateFolder, enableDemodularization: true, skipSummaryInfo: false); + var output = unbindCommand.Execute(); + var extractedFilePaths = unbindCommand.ExportedFiles; + + var decompilerHelper = context.ServiceProvider.GetService(); + var decompiler = new Decompiler(this.Messaging, backendHelper, decompilerHelper, context.Extensions, context.ExtensionData, context.SymbolDefinitionCreator, context.BaseSourcePath, context.SuppressCustomTables, context.SuppressDroppingEmptyTables, context.SuppressRelativeActionSequencing, context.SuppressUI, context.TreatProductAsModule); + var document = decompiler.Decompile(output); + + var result = context.ServiceProvider.GetService(); + result.Document = document; + result.Platform = GetPlatformFromOutput(output); + result.ExtractedFilePaths = extractedFilePaths.ToList(); return result; } diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs index a0798e62..025affd3 100644 --- a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs +++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs @@ -4,7 +4,6 @@ namespace WixToolset.Core.Bind { using System; using System.Collections.Generic; - using System.Globalization; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -41,9 +40,9 @@ namespace WixToolset.Core.Bind { var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); var unique = this.HashUri(uri.AbsoluteUri); - var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); + var extractedName = String.Concat(localFileNameWithoutExtension, "_", unique); - extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); + extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName, embeddedFileId)); extracts.Add(embeddedFileId, extractPath); } diff --git a/src/wix/WixToolset.Core/Bind/FileResolver.cs b/src/wix/WixToolset.Core/Bind/FileResolver.cs deleted file mode 100644 index eb878239..00000000 --- a/src/wix/WixToolset.Core/Bind/FileResolver.cs +++ /dev/null @@ -1,207 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class FileResolver - { - private const string BindPathOpenString = "!(bindpath."; - - private FileResolver(IEnumerable bindPaths) - { - this.BindPaths = (bindPaths ?? Array.Empty()).ToLookup(b => b.Stage); - this.RebaseTarget = this.BindPaths[BindStage.Target].Any(); - this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); - } - - public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) - { - this.ResolverExtensions = extensions ?? Array.Empty(); - } - - public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) - { - this.LibrarianExtensions = extensions ?? Array.Empty(); - } - - private ILookup BindPaths { get; } - - public bool RebaseTarget { get; } - - public bool RebaseUpdated { get; } - - private IEnumerable ResolverExtensions { get; } - - private IEnumerable LibrarianExtensions { get; } - - public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source) - { - var checkedPaths = new List(); - - foreach (var extension in this.LibrarianExtensions) - { - var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths); - } - - /// - /// Resolves the source path of a file using binder extensions. - /// - /// Original source value. - /// Optional type of source file being resolved. - /// Optional source line of source file being resolved. - /// The binding stage used to determine what collection of bind paths will be used - /// Optional collection of paths already checked. - /// Should return a valid path for the stream to be imported. - public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable alreadyCheckedPaths = null) - { - var checkedPaths = new List(); - - if (alreadyCheckedPaths != null) - { - checkedPaths.AddRange(alreadyCheckedPaths); - } - - foreach (var extension in this.ResolverExtensions) - { - var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths); - } - - private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List checkedPaths) - { - string resolved = null; - - // If the file exists, we're good to go. - checkedPaths.Add(source); - if (CheckFileExists(source)) - { - resolved = source; - } - else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. - { - resolved = null; - } - else // not a rooted path so let's try applying all the different source resolution options. - { - var bindName = String.Empty; - var path = source; - var pathWithoutSourceDir = String.Empty; - - if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) - { - var closeParen = source.IndexOf(')', BindPathOpenString.Length); - - if (-1 != closeParen) - { - bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); - path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. - path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. - } - } - else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) - { - pathWithoutSourceDir = path.Substring(10); - } - - var bindPaths = this.BindPaths[bindStage]; - - foreach (var bindPath in bindPaths) - { - if (String.IsNullOrEmpty(bindName)) - { - if (String.IsNullOrEmpty(bindPath.Name)) - { - if (!String.IsNullOrEmpty(pathWithoutSourceDir)) - { - var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - - if (String.IsNullOrEmpty(resolved)) - { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - } - } - else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) - { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - - if (!String.IsNullOrEmpty(resolved)) - { - break; - } - } - } - - if (null == resolved) - { - throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths)); - } - - return resolved; - } - - private static bool CheckFileExists(string path) - { - try - { - return File.Exists(path); - } - catch (ArgumentException) - { - throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); - } - } - } -} diff --git a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs index 794208e5..8a5299d9 100644 --- a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs @@ -16,186 +16,121 @@ namespace WixToolset.Core.Bind /// internal class ResolveFieldsCommand { - public IMessaging Messaging { private get; set; } + public ResolveFieldsCommand(IMessaging messaging, IFileResolver fileResolver, IVariableResolver variableResolver, IReadOnlyCollection bindPaths, IReadOnlyCollection extensions, ExtractEmbeddedFiles filesWithEmbeddedFiles, string intermediateFolder, Intermediate intermediate, bool allowUnresolvedVariables) + { + this.Messaging = messaging; + this.FileResolver = fileResolver; + this.VariableResolver = variableResolver; + this.BindPaths = bindPaths; + this.Extensions = extensions; + this.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + this.IntermediateFolder = intermediateFolder; + this.Intermediate = intermediate; + this.AllowUnresolvedVariables = allowUnresolvedVariables; + } - public bool BuildingPatch { private get; set; } + private IMessaging Messaging { get; } - public IVariableResolver VariableResolver { private get; set; } + private IFileResolver FileResolver { get; } - public IEnumerable BindPaths { private get; set; } + private IVariableResolver VariableResolver { get; } - public IEnumerable Extensions { private get; set; } + private IEnumerable BindPaths { get; } - public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } + private IEnumerable Extensions { get; } - public string IntermediateFolder { private get; set; } + private ExtractEmbeddedFiles FilesWithEmbeddedFiles { get; } - public Intermediate Intermediate { private get; set; } + private string IntermediateFolder { get; } - public bool SupportDelayedResolution { private get; set; } + private Intermediate Intermediate { get; } - public bool AllowUnresolvedVariables { private get; set; } + private bool AllowUnresolvedVariables { get; } public IReadOnlyCollection DelayedFields { get; private set; } public void Execute() { - var delayedFields = this.SupportDelayedResolution ? new List() : null; + var delayedFields = new List(); - var fileResolver = new FileResolver(this.BindPaths, this.Extensions); + var bindPaths = this.BindPaths.Where(b => b.Stage == BindStage.Normal).ToList(); // Build the column lookup only when needed. Dictionary customColumnsById = null; - foreach (var sections in this.Intermediate.Sections) + foreach (var symbol in this.Intermediate.Sections.SelectMany(s => s.Symbols)) { - foreach (var symbol in sections.Symbols) + foreach (var field in symbol.Fields.Where(f => !f.IsNull())) { - foreach (var field in symbol.Fields) + var fieldType = field.Type; + + // Custom table cells require an extra look up to the column definition as the + // cell's data type is always a string (because strings can store anything) but + // the column definition may be more specific. + if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell) { - if (field.IsNull()) + // We only care about the Data in a CustomTable cell. + if (field.Name != nameof(WixCustomTableCellSymbolFields.Data)) { continue; } - var fieldType = field.Type; - - // Custom table cells require an extra look up to the column definition as the - // cell's data type is always a string (because strings can store anything) but - // the column definition may be more specific. - if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell) + if (customColumnsById == null) { - // We only care about the Data in a CustomTable cell. - if (field.Name != nameof(WixCustomTableCellSymbolFields.Data)) - { - continue; - } - - if (customColumnsById == null) - { - customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType()).ToDictionary(t => t.Id.Id); - } - - if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn)) - { - fieldType = customColumn.Type; - } + customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType()).ToDictionary(t => t.Id.Id); } - // Check to make sure we're in a scenario where we can handle variable resolution. - if (null != delayedFields) + if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn)) { - // resolve localization and wix variables - if (fieldType == IntermediateFieldType.String) - { - var original = field.AsString(); - if (!String.IsNullOrEmpty(original)) - { - var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); - if (resolution.UpdatedValue) - { - field.Set(resolution.Value); - } - - if (resolution.DelayedResolve) - { - delayedFields.Add(new DelayedField(symbol, field)); - } - } - } - } - - // Move to next symbol if we've hit an error resolving variables. - if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. - { - continue; + fieldType = customColumn.Type; } + } - // Resolve file paths - if (fieldType == IntermediateFieldType.Path) + // Check to make sure we're in a scenario where we can handle variable resolution. + if (null != delayedFields) + { + // resolve localization and wix variables + if (fieldType == IntermediateFieldType.String) { - this.ResolvePathField(fileResolver, symbol, field); - -#if TODO_PATCHING - if (null != objectField.PreviousData) + var original = field.AsString(); + if (!String.IsNullOrEmpty(original)) { - objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault); - - if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. + var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); + if (resolution.UpdatedValue) { - // file is compressed in a cabinet (and not modified above) - if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault) - { - // when loading transforms from disk, PreviousBaseUri may not have been set - if (null == objectField.PreviousBaseUri) - { - objectField.PreviousBaseUri = objectField.BaseUri; - } - - string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder); - - // set the path to the file once its extracted from the cabinet - objectField.PreviousData = extractPath; - } - else if (null != objectField.PreviousData) // non-compressed file (or localized value) - { - try - { - if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) - { - // resolve the path to the file - objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal); - } - else - { - if (fileResolver.RebaseTarget) - { - // if -bt is used, it come here - // Try to use the original unresolved source from either target build or update build - // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here - // If target is old version and updated is new version, it uses unresolved path from updated build - // If both target and updated are of new versions, it uses unresolved path from target build - if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData) - { - objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData; - } - } - - // resolve the path to the file - objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target); + field.Set(resolution.Value); + } - } - } - catch (WixFileNotFoundException) - { - // display the error with source line information - Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData)); - } - } + if (resolution.DelayedResolve) + { + delayedFields.Add(new DelayedField(symbol, field)); } } -#endif } } + + // Move to next symbol if we've hit an error resolving variables. + if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. + { + continue; + } + + // Resolve file paths + if (fieldType == IntermediateFieldType.Path) + { + this.ResolvePathField(this.FileResolver, bindPaths, symbol, field); + } } } this.DelayedFields = delayedFields; } - private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field) + private void ResolvePathField(IFileResolver fileResolver, IEnumerable bindPaths, IntermediateSymbol symbol, IntermediateField field) { var fieldValue = field.AsPath(); var originalFieldPath = fieldValue.Path; -#if TODO_PATCHING - // Skip file resolution if the file is to be deleted. - if (RowOperation.Delete == symbol.Operation) - { - continue; - } -#endif - // If the file is embedded and if the previous value has a bind variable in the path // which gets modified by resolving the previous value again then switch to that newly // resolved path instead of using the embedded file. @@ -221,45 +156,7 @@ namespace WixToolset.Core.Bind { try { - var resolvedPath = fieldValue.Path; - - if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) - { -#if TODO_PATCHING - // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file - if (null == objectField.UnresolvedData) - { - objectField.UnresolvedData = (string)objectField.Data; - } -#endif - resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); - } - else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) - { - resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); - } -#if TODO_PATCHING - else // Re-base binding path scenario caused by pyro.exe -bt -bu - { - // by default, use the resolved Data for file lookup - string filePathToResolve = (string)objectField.Data; - - // if -bu is used in pyro command, this condition holds true and the tool - // will use pre-resolved source for new wixpdb file - if (fileResolver.RebaseUpdated) - { - // try to use the unResolved Source if it exists. - // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll - // Old version of winpdb file does not contain this attribute and the value is null. - if (null != objectField.UnresolvedData) - { - filePathToResolve = objectField.UnresolvedData; - } - } - - objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated); - } -#endif + var resolvedPath = fileResolver.ResolveFile(fieldValue.Path, this.Extensions, bindPaths, BindStage.Normal, symbol.SourceLineNumbers, symbol.Definition); if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/wix/WixToolset.Core/BindContext.cs b/src/wix/WixToolset.Core/BindContext.cs index 9dd6aa46..1c1f7528 100644 --- a/src/wix/WixToolset.Core/BindContext.cs +++ b/src/wix/WixToolset.Core/BindContext.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core public IServiceProvider ServiceProvider { get; } - public IReadOnlyCollection BindPaths { get; set; } + public IReadOnlyCollection BindPaths { get; set; } public string BurnStubPath { get; set; } diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs index dc4e0a6d..34520fc0 100644 --- a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs @@ -48,7 +48,9 @@ namespace WixToolset.Core.CommandLine { new CommandLineHelpSwitch("-arch", "Set the architecture of the output."), new CommandLineHelpSwitch("-bindfiles", "-bf", "Bind files into an output .wixlib. Ignored if not building a .wixlib."), - new CommandLineHelpSwitch("-bindpath", "Bind path to search for content files."), + new CommandLineHelpSwitch("-bindpath", "-b", "Bind path to search for content files."), + new CommandLineHelpSwitch("-bindpath:target", "-bt", "Bind path to search for target package's content files. Only used when building a patch."), + new CommandLineHelpSwitch("-bindpath:update", "-bu", "Bind path to search for update package's content files. Only used when building a patch."), new CommandLineHelpSwitch("-cabcache", "-cc", "Set a folder to cache cabinets across builds."), new CommandLineHelpSwitch("-culture", "Adds a culture to filter localization files."), new CommandLineHelpSwitch("-define", "-d", "Sets a preprocessor variable."), @@ -292,6 +294,7 @@ namespace WixToolset.Core.CommandLine { { var context = this.ServiceProvider.GetService(); + context.BindPaths = bindPaths; //context.CabbingThreadCount = this.CabbingThreadCount; context.CabCachePath = cabCachePath; context.ResolvedCodepage = resolveResult.Codepage; @@ -534,11 +537,25 @@ namespace WixToolset.Core.CommandLine this.BindFiles = true; return true; + case "b": case "bindpath": + case "bt": + case "bindpath:target": + case "bu": + case "bindpath:update": { var value = parser.GetNextArgumentOrError(arg); - if (value != null && this.TryParseBindPath(value, out var bindPath)) + if (value != null && this.TryParseBindPath(arg, value, out var bindPath)) { + if (parameter == "bt" || parameter.EndsWith("target")) + { + bindPath.Stage = BindStage.Target; + } + else if (parameter == "bu" || parameter.EndsWith("update")) + { + bindPath.Stage = BindStage.Updated; + } + this.BindPaths.Add(bindPath); } return true; @@ -830,7 +847,7 @@ namespace WixToolset.Core.CommandLine return new InputsAndOutputs(codePaths, localizationPaths, libraryPaths, wixipls, outputPath, outputType, pdbPath, this.PdbType); } - private bool TryParseBindPath(string bindPath, out IBindPath bp) + private bool TryParseBindPath(string argument, string bindPath, out IBindPath bp) { var namedPath = bindPath.Split(BindPathSplit, 2); @@ -848,7 +865,7 @@ namespace WixToolset.Core.CommandLine if (File.Exists(bp.Path)) { - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(argument, bp.Path)); return false; } diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs index d04ae491..8f1ae7fb 100644 --- a/src/wix/WixToolset.Core/Compiler.cs +++ b/src/wix/WixToolset.Core/Compiler.cs @@ -7706,7 +7706,6 @@ namespace WixToolset.Core var validationFlags = TransformFlags.PatchTransformDefault; string baselineFile = null; string updateFile = null; - string transformFile = null; foreach (var attrib in node.Attributes()) { @@ -7726,9 +7725,6 @@ namespace WixToolset.Core case "UpdateFile": updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; - case "TransformFile": - transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -7746,26 +7742,14 @@ namespace WixToolset.Core id = Identifier.Invalid; } - if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) + if (String.IsNullOrEmpty(baselineFile)) { - if (String.IsNullOrEmpty(baselineFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile")); - } - - if (String.IsNullOrEmpty(updateFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile")); - } - - if (!String.IsNullOrEmpty(transformFile)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile")); - } + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile")); } - else if (String.IsNullOrEmpty(transformFile)) + + if (String.IsNullOrEmpty(updateFile)) { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile")); } foreach (var child in node.Elements()) @@ -7805,7 +7789,6 @@ namespace WixToolset.Core ValidationFlags = validationFlags, BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, - TransformFile = new IntermediateFieldPathValue { Path = transformFile }, }); } } diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs index 20a47637..4a5cc607 100644 --- a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs +++ b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs @@ -6,8 +6,6 @@ namespace WixToolset.Core.ExtensibilityServices using System.Collections.Generic; using WixToolset.Core.Bind; using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; @@ -17,21 +15,6 @@ namespace WixToolset.Core.ExtensibilityServices { } - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) - { - return new FileFacade(file, assembly); - } - - public IFileFacade CreateFileFacade(FileRow fileRow) - { - return new FileFacade(fileRow); - } - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) - { - return new FileFacade(true, fileSymbol); - } - public string CreateGuid() { return Common.GenerateGuid(); diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs deleted file mode 100644 index 65043658..00000000 --- a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs +++ /dev/null @@ -1,175 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - - internal class FileFacade : IFileFacade - { - public FileFacade(FileSymbol file, AssemblySymbol assembly) - { - this.FileSymbol = file; - this.AssemblySymbol = assembly; - - this.Identifier = file.Id; - this.ComponentRef = file.ComponentRef; - } - - public FileFacade(bool fromModule, FileSymbol file) - { - this.FromModule = fromModule; - this.FileSymbol = file; - - this.Identifier = file.Id; - this.ComponentRef = file.ComponentRef; - } - - public FileFacade(FileRow row) - { - this.FromTransform = true; - this.FileRow = row; - - this.Identifier = new Identifier(AccessModifier.Section, row.File); - this.ComponentRef = row.Component; - } - - public bool FromModule { get; } - - public bool FromTransform { get; } - - private FileRow FileRow { get; } - - private FileSymbol FileSymbol { get; } - - private AssemblySymbol AssemblySymbol { get; } - - public string Id => this.Identifier.Id; - - public Identifier Identifier { get; } - - public string ComponentRef { get; } - - public int DiskId - { - get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId; - set - { - if (this.FileRow == null) - { - this.FileSymbol.DiskId = value; - } - else - { - this.FileRow.DiskId = value; - } - } - } - - public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName; - - public int FileSize - { - get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize; - set - { - if (this.FileRow == null) - { - this.FileSymbol.FileSize = value; - } - else - { - this.FileRow.FileSize = value; - } - } - } - - public string Language - { - get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Language = value; - } - else - { - this.FileRow.Language = value; - } - } - } - - public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null; - - public int Sequence - { - get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Sequence = value; - } - else - { - this.FileRow.Sequence = value; - } - } - } - - public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; - - public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; - - public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; - - public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - - public string Version - { - get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Version = value; - } - else - { - this.FileRow.Version = value; - } - } - } - - public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null; - - public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException(); - - public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException(); - - /// - /// Gets the set of MsiAssemblyName rows created for this file. - /// - /// RowCollection of MsiAssemblyName table. - public List AssemblyNames { get; set; } - - /// - /// Gets or sets the MsiFileHash row for this file. - /// - public MsiFileHashSymbol Hash { get; set; } - - /// - /// Allows direct access to the underlying FileRow as requried for patching. - /// - public FileRow GetFileRow() - { - return this.FileRow ?? throw new NotImplementedException(); - } - } -} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileResolver.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileResolver.cs new file mode 100644 index 00000000..8f08e75e --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileResolver.cs @@ -0,0 +1,165 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class FileResolver : IFileResolver + { + private const string BindPathOpenString = "!(bindpath."; + + public string ResolveFile(string source, IEnumerable librarianExtensions, IEnumerable bindPaths, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition) + { + var checkedPaths = new List(); + + foreach (var extension in librarianExtensions) + { + var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindPaths, checkedPaths); + } + + public string ResolveFile(string source, IEnumerable resolverExtensions, IEnumerable bindPaths, BindStage bindStage, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, IEnumerable alreadyCheckedPaths = null) + { + var checkedPaths = new List(); + + if (alreadyCheckedPaths != null) + { + checkedPaths.AddRange(alreadyCheckedPaths); + } + + foreach (var extension in resolverExtensions) + { + var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindPaths, checkedPaths); + } + + private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, IEnumerable bindPaths, List checkedPaths) + { + string resolved = null; + + // If the file exists, we're good to go. + checkedPaths.Add(source); + if (CheckFileExists(source)) + { + resolved = source; + } + else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. + { + resolved = null; + } + else // not a rooted path so let's try applying all the different source resolution options. + { + var bindName = String.Empty; + var path = source; + var pathWithoutSourceDir = String.Empty; + + if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) + { + var closeParen = source.IndexOf(')', BindPathOpenString.Length); + + if (-1 != closeParen) + { + bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); + path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. + path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. + } + } + else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) + { + pathWithoutSourceDir = path.Substring(10); + } + + foreach (var bindPath in bindPaths) + { + if (String.IsNullOrEmpty(bindName)) + { + if (String.IsNullOrEmpty(bindPath.Name)) + { + if (!String.IsNullOrEmpty(pathWithoutSourceDir)) + { + resolved = ResolveWithBindPath(bindPath.Path, pathWithoutSourceDir, checkedPaths); + } + + if (String.IsNullOrEmpty(resolved)) + { + resolved = ResolveWithBindPath(bindPath.Path, path, checkedPaths); + } + } + } + else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) + { + resolved = ResolveWithBindPath(bindPath.Path, path, checkedPaths); + } + + if (!String.IsNullOrEmpty(resolved)) + { + break; + } + } + } + + if (null == resolved) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition?.Name, checkedPaths)); + } + + return resolved; + } + + private static string ResolveWithBindPath(string bindPath, string relativePath, List checkedPaths) + { + var filePath = Path.Combine(bindPath, relativePath); + + checkedPaths.Add(filePath); + + if (CheckFileExists(filePath)) + { + return filePath; + } + + return null; + } + + private static bool CheckFileExists(string path) + { + try + { + return File.Exists(path); + } + catch (ArgumentException) + { + throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Librarian.cs b/src/wix/WixToolset.Core/Librarian.cs index 2762fb33..968dd946 100644 --- a/src/wix/WixToolset.Core/Librarian.cs +++ b/src/wix/WixToolset.Core/Librarian.cs @@ -5,7 +5,6 @@ namespace WixToolset.Core using System; using System.Collections.Generic; using System.Linq; - using WixToolset.Core.Bind; using WixToolset.Core.Link; using WixToolset.Data; using WixToolset.Extensibility.Data; @@ -21,6 +20,7 @@ namespace WixToolset.Core this.ServiceProvider = serviceProvider; this.Messaging = this.ServiceProvider.GetService(); + this.FileResolver = this.ServiceProvider.GetService(); this.LayoutServices = this.ServiceProvider.GetService(); } @@ -28,6 +28,8 @@ namespace WixToolset.Core private IMessaging Messaging { get; } + private IFileResolver FileResolver { get; } + private ILayoutServices LayoutServices { get; } /// @@ -97,7 +99,7 @@ namespace WixToolset.Core { var variableResolver = this.ServiceProvider.GetService(); - var fileResolver = new FileResolver(context.BindPaths, context.Extensions); + var bindPaths = context.BindPaths.Where(b => b.Stage == BindStage.Normal).ToList(); foreach (var symbol in sections.SelectMany(s => s.Symbols)) { @@ -109,7 +111,7 @@ namespace WixToolset.Core { var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path); - var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value); + var file = this.FileResolver.ResolveFile(resolution.Value, context.Extensions, bindPaths, symbol.SourceLineNumbers, symbol.Definition); if (!String.IsNullOrEmpty(file)) { diff --git a/src/wix/WixToolset.Core/Resolver.cs b/src/wix/WixToolset.Core/Resolver.cs index e93f8e1b..f7aa6ff9 100644 --- a/src/wix/WixToolset.Core/Resolver.cs +++ b/src/wix/WixToolset.Core/Resolver.cs @@ -23,12 +23,16 @@ namespace WixToolset.Core this.ServiceProvider = serviceProvider; this.Messaging = serviceProvider.GetService(); + + this.FileResolver = serviceProvider.GetService(); } private IServiceProvider ServiceProvider { get; } private IMessaging Messaging { get; } + private IFileResolver FileResolver { get; } + public IResolveResult Resolve(IResolveContext context) { foreach (var extension in context.Extensions) @@ -45,7 +49,7 @@ namespace WixToolset.Core this.LocalizeUI(variableResolver, context.IntermediateRepresentation); - resolveResult = this.DoResolve(context, variableResolver); + resolveResult = this.ResolveFields(context, variableResolver); var primaryLocalization = filteredLocalizations.FirstOrDefault(); @@ -71,49 +75,18 @@ namespace WixToolset.Core return resolveResult; } - private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) + private ResolveResult ResolveFields(IResolveContext context, IVariableResolver variableResolver) { - var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); - var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); IReadOnlyCollection delayedFields; { - var command = new ResolveFieldsCommand(); - command.Messaging = this.Messaging; - command.BuildingPatch = buildingPatch; - command.VariableResolver = variableResolver; - command.BindPaths = context.BindPaths; - command.Extensions = context.Extensions; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.IntermediateFolder = context.IntermediateFolder; - command.Intermediate = context.IntermediateRepresentation; - command.SupportDelayedResolution = true; - command.AllowUnresolvedVariables = context.AllowUnresolvedVariables; + var command = new ResolveFieldsCommand(this.Messaging, this.FileResolver, variableResolver, context.BindPaths, context.Extensions, filesWithEmbeddedFiles, context.IntermediateFolder, context.IntermediateRepresentation, context.AllowUnresolvedVariables); command.Execute(); delayedFields = command.DelayedFields; } -#if TODO_PATCHING - if (context.IntermediateRepresentation.SubStorages != null) - { - foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages) - { - var command = new ResolveFieldsCommand(); - command.BuildingPatch = buildingPatch; - command.BindVariableResolver = context.WixVariableResolver; - command.BindPaths = context.BindPaths; - command.Extensions = context.Extensions; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.IntermediateFolder = context.IntermediateFolder; - command.Intermediate = context.IntermediateRepresentation; - command.SupportDelayedResolution = false; - command.Execute(); - } - } -#endif - var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved); diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs index fa6b369d..5620bcd2 100644 --- a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs +++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs @@ -27,6 +27,7 @@ namespace WixToolset.Core this.AddService((provider, singletons) => AddSingleton(singletons, new LayoutServices(provider))); this.AddService((provider, singletons) => AddSingleton(singletons, new BackendHelper(provider))); this.AddService((provider, singletons) => AddSingleton(singletons, new PathResolver())); + this.AddService((provider, singletons) => AddSingleton(singletons, new FileResolver())); this.AddService((provider, singletons) => AddSingleton(singletons, new FileSystem())); this.AddService((provider, singletons) => AddSingleton(singletons, new WixBranding())); diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs index a8aff8d8..c330fd02 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -5,6 +5,7 @@ namespace WixToolsetTest.CoreIntegration using System; using System.Collections.Generic; using System.ComponentModel; + using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -22,8 +23,11 @@ namespace WixToolsetTest.CoreIntegration public class PatchFixture : IDisposable { private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; + private static readonly XName TargetProductCodeName = PatchNamespace + "TargetProductCode"; + private readonly DisposableFileSystem tempFileSystem; private readonly string tempBaseFolder; + private readonly string templateSourceFolder; private readonly string templateBaselinePdb; private readonly string templateUpdatePdb; private readonly string templateUpdateNoFilesChangedPdb; @@ -33,14 +37,14 @@ namespace WixToolsetTest.CoreIntegration this.tempFileSystem = new DisposableFileSystem(); this.tempBaseFolder = this.tempFileSystem.GetFolder(); - var templateSourceFolder = TestData.Get(@"TestData", "PatchTemplatePackage"); + this.templateSourceFolder = TestData.Get(@"TestData", "PatchTemplatePackage"); var tempFolderBaseline = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "baseline"); var tempFolderUpdate = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "update"); var tempFolderUpdateNoFileChanges = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "updatewithoutfilechanges"); - this.templateBaselinePdb = BuildMsi("Baseline.msi", templateSourceFolder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0", new[] { Path.Combine(templateSourceFolder, ".baseline-data") }); - this.templateUpdatePdb = BuildMsi("Update.msi", templateSourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(templateSourceFolder, ".update-data") }); - this.templateUpdateNoFilesChangedPdb = BuildMsi("Update.msi", templateSourceFolder, tempFolderUpdateNoFileChanges, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(templateSourceFolder, ".baseline-data") }); + this.templateBaselinePdb = BuildMsi("Baseline.msi", this.templateSourceFolder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0", new[] { Path.Combine(this.templateSourceFolder, ".baseline-data") }); + this.templateUpdatePdb = BuildMsi("Update.msi", this.templateSourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(this.templateSourceFolder, ".update-data") }); + this.templateUpdateNoFilesChangedPdb = BuildMsi("Update.msi", this.templateSourceFolder, tempFolderUpdateNoFileChanges, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(this.templateSourceFolder, ".baseline-data") }); } public void Dispose() @@ -49,7 +53,7 @@ namespace WixToolsetTest.CoreIntegration } [Fact] - public void CanBuildSimplePatch() + public void CanBuildSimplePatchUsingWixpdbs() { var folder = TestData.Get(@"TestData", "PatchSingle"); @@ -66,14 +70,13 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(update1Pdb)); var doc = GetExtractPatchXml(patchPath); - WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(TargetProductCodeName).Value); var names = Query.GetSubStorageNames(patchPath); WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); var cab = Path.Combine(tempFolder, "foo.cab"); Query.ExtractStream(patchPath, "foo.cab", cab); - Assert.True(File.Exists(cab)); var files = Query.GetCabinetFiles(cab); WixAssert.CompareLineByLine(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); @@ -81,7 +84,37 @@ namespace WixToolsetTest.CoreIntegration } [Fact] - public void CanBuildSimplePatchWithFileChanges() + public void CanBuildSimplePatchWithFileChangesUsingMsi() + { + var sourceFolder = TestData.Get(@"TestData", "PatchWithFileChangesUsingMsi"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var tempFolderPatch = Path.Combine(baseFolder, "patch"); + + var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(this.templateBaselinePdb), Path.GetDirectoryName(this.templateUpdatePdb) }); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + var doc = GetExtractPatchXml(patchPath); + WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value); + + var names = Query.GetSubStorageNames(patchPath); + WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(baseFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + + var files = Query.GetCabinetFiles(cab); + var file = files.Single(); + WixAssert.StringEqual("a.txt", file.Name); + var contents = file.OpenText().ReadToEnd(); + WixAssert.StringEqual("This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.\r\n", contents); + } + } + + [Fact] + public void CanBuildSimplePatchWithFileChangesUsingWixpdb() { var sourceFolder = TestData.Get(@"TestData", "PatchWithFileChanges"); @@ -94,7 +127,7 @@ namespace WixToolsetTest.CoreIntegration var patchPath = Path.ChangeExtension(patchPdb, ".msp"); var doc = GetExtractPatchXml(patchPath); - WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value); var names = Query.GetSubStorageNames(patchPath); WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); @@ -106,10 +139,72 @@ namespace WixToolsetTest.CoreIntegration var file = files.Single(); WixAssert.StringEqual("a.txt", file.Name); var contents = file.OpenText().ReadToEnd(); - WixAssert.StringEqual("This is A v1.0.1\r\n", contents); + WixAssert.StringEqual("This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.\r\n", contents); } } + [Fact] + public void CanBuildSimplePatchWithFileChangesUsingWixpdbAndAlternativeUpdatedSourceFolder() + { + var sourceFolder = TestData.Get(@"TestData", "PatchWithFileChanges"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var tempFolderPatch = Path.Combine(baseFolder, "patch"); + + var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(this.templateBaselinePdb), Path.GetDirectoryName(this.templateUpdatePdb) }, updateBindpaths: new[] { Path.Combine(this.templateSourceFolder, ".update-data-alternative") }); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + var doc = GetExtractPatchXml(patchPath); + WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value); + + var names = Query.GetSubStorageNames(patchPath); + WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(baseFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + + var files = Query.GetCabinetFiles(cab); + var file = files.Single(); + WixAssert.StringEqual("a.txt", file.Name); + var contents = file.OpenText().ReadToEnd(); + WixAssert.StringEqual("This is A v1.0.1 from the '.update-data-alternative' folder in 'PatchTemplatePackage'.\r\n\r\nDiam quis enim lobortis scelerisque fermentum dui faucibus in ornare.\r\n", contents); + } + } + + [Fact] + public void CanBuildPatchFromAdminImage() + { + var sourceFolder = TestData.Get(@"TestData", "PatchUsingAdminImages"); + + var baseFolder = this.tempFileSystem.GetFolder(); + var tempFolderPatch = Path.Combine(baseFolder, "patch"); + var adminBaselineFolder = Path.Combine(baseFolder, "admin-baseline"); + var adminUpdateFolder = Path.Combine(baseFolder, "admin-update"); + + CreateAdminImage(this.templateBaselinePdb, adminBaselineFolder); + CreateAdminImage(this.templateUpdatePdb, adminUpdateFolder); + + var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { adminBaselineFolder, adminUpdateFolder }); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + var doc = GetExtractPatchXml(patchPath); + WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value); + + var names = Query.GetSubStorageNames(patchPath); + WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(baseFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + + var files = Query.GetCabinetFiles(cab); + var file = files.Single(); + WixAssert.StringEqual("a.txt", file.Name); + var contents = file.OpenText().ReadToEnd(); + WixAssert.StringEqual("This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.\r\n", contents); + } + [Fact] public void CanBuildSimplePatchWithNoFileChanges() { @@ -123,14 +218,13 @@ namespace WixToolsetTest.CoreIntegration var patchPath = Path.ChangeExtension(patchPdb, ".msp"); var doc = GetExtractPatchXml(patchPath); - WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value); var names = Query.GetSubStorageNames(patchPath); WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); var cab = Path.Combine(tempFolder, "foo.cab"); Query.ExtractStream(patchPath, "foo.cab", cab); - Assert.True(File.Exists(cab)); var files = Query.GetCabinetFiles(cab); Assert.Empty(files); @@ -155,14 +249,13 @@ namespace WixToolsetTest.CoreIntegration var patchPath = Path.ChangeExtension(patchPdb, ".msp"); var doc = GetExtractPatchXml(patchPath); - WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(TargetProductCodeName).Value); var names = Query.GetSubStorageNames(patchPath); WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); var cab = Path.Combine(baseFolder, "foo.cab"); Query.ExtractStream(patchPath, "foo.cab", cab); - Assert.True(File.Exists(cab)); var files = Query.GetCabinetFiles(cab); var file = files.Single(); @@ -197,7 +290,7 @@ namespace WixToolsetTest.CoreIntegration var patchPath = Path.ChangeExtension(patchPdb, ".msp"); var doc = GetExtractPatchXml(patchPath); - WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value); var names = Query.GetSubStorageNames(patchPath); WixAssert.CompareLineByLine(new[] { "#ThisBaseLineIdIsTooLongAndGe.1", "ThisBaseLineIdIsTooLongAndGe.1" }, names); @@ -217,12 +310,12 @@ namespace WixToolsetTest.CoreIntegration var tempFolderPatch = Path.Combine(baseFolder, "patch"); var baselinePdb = BuildMsi("Baseline.msi", sourceFolder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", sourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true); + var updatePdb = BuildMsi("Update.msi", sourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(updatePdb) }, hasNoFiles: true); var patchPath = Path.ChangeExtension(patchPdb, ".msp"); - Assert.True(File.Exists(baselinePdb)); - Assert.True(File.Exists(update1Pdb)); + var doc = GetExtractPatchXml(patchPath); + WixAssert.StringEqual("{7C871EC1-1F89-4850-A6A9-D7A4C21769F6}", doc.Root.Element(TargetProductCodeName).Value); } } @@ -317,7 +410,28 @@ namespace WixToolsetTest.CoreIntegration return Path.ChangeExtension(outputPath, ".wixpdb"); } - private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable bindpaths = null, bool hasNoFiles = false, bool warningsAsErrors = true) + private static string BuildMst(string transformName, string baseFolder, string templateBaselinePdb, string templateUpdatePdb) + { + var outputPath = Path.Combine(baseFolder, transformName); + + var args = new List + { + "msi", "transform", + templateBaselinePdb, + templateUpdatePdb, + "-intermediateFolder", Path.Combine(baseFolder), + "-t", "patch", + "-o", outputPath, + }; + + var result = WixRunner.Execute(args.ToArray()); + + result.AssertSuccess(); + + return outputPath; + } + + private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable bindpaths = null, IEnumerable targetBindpaths = null, IEnumerable updateBindpaths = null, bool hasNoFiles = false, bool warningsAsErrors = true) { var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); @@ -332,10 +446,22 @@ namespace WixToolsetTest.CoreIntegration "-o", outputPath }; - foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty()) + foreach (var additionalBindPath in bindpaths ?? Enumerable.Empty()) { args.Add("-bindpath"); - args.Add(additionaBindPath); + args.Add(additionalBindPath); + } + + foreach (var targetBindPath in targetBindpaths ?? Enumerable.Empty()) + { + args.Add("-bindpath:target"); + args.Add(targetBindPath); + } + + foreach (var updateBindpath in updateBindpaths ?? Enumerable.Empty()) + { + args.Add("-bindpath:update"); + args.Add(updateBindpath); } var result = WixRunner.Execute(warningsAsErrors, args.ToArray()); @@ -365,6 +491,16 @@ namespace WixToolsetTest.CoreIntegration return Path.ChangeExtension(outputPath, ".wixpdb"); } + private static void CreateAdminImage(string msiPath, string targetDir) + { + var args = $"/a {Path.ChangeExtension(msiPath, "msi")} TARGETDIR={targetDir} /qn"; + + var proc = Process.Start("msiexec.exe", args); + proc.WaitForExit(5000); + + Assert.Equal(0, proc.ExitCode); + } + private static XDocument GetExtractPatchXml(string path) { var buffer = new StringBuilder(65535); diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt index 6fd385bd..f6c69979 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt @@ -1 +1 @@ -This is A v1.0.0 +This is A v1.0.0 from the "PatchTemplatePackage\.baseline-data" folder. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt new file mode 100644 index 00000000..17e8c861 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt @@ -0,0 +1,3 @@ +This is A v1.0.1 from the '.update-data-alternative' folder in 'PatchTemplatePackage'. + +Diam quis enim lobortis scelerisque fermentum dui faucibus in ornare. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt index f7ec8b4e..26832a47 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt @@ -1 +1,3 @@ -This is A v1.0.1 +This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs new file mode 100644 index 00000000..f9ea1f5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs new file mode 100644 index 00000000..f9ea1f5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + -- cgit v1.2.3-55-g6feb