From dbde9e7104b907bbbaea17e21247d8cafc8b3a4c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 14 Oct 2017 16:12:07 -0700 Subject: Massive refactoring to introduce the concept of IBackend --- WixToolset.Core.sln | 58 +- .../WixToolset.BuildTasks.csproj | 2 + src/WixToolset.Core.Burn/BackendFactory.cs | 30 + src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 942 +++++++++++++ .../Bind/ProvidesDependency.cs | 108 ++ .../Bind/ProvidesDependencyCollection.cs | 64 + .../Bind/WixComponentSearchInfo.cs | 64 + src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs | 54 + .../Bind/WixProductSearchInfo.cs | 67 + .../Bind/WixRegistrySearchInfo.cs | 92 ++ src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs | 53 + src/WixToolset.Core.Burn/BundleBackend.cs | 58 + .../AutomaticallySlipstreamPatchesCommand.cs | 112 ++ src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 378 ++++++ src/WixToolset.Core.Burn/Bundles/BurnReader.cs | 220 +++ src/WixToolset.Core.Burn/Bundles/BurnWriter.cs | 239 ++++ ...CreateBootstrapperApplicationManifestCommand.cs | 241 ++++ .../Bundles/CreateBurnManifestCommand.cs | 686 ++++++++++ .../Bundles/CreateContainerCommand.cs | 68 + .../Bundles/GetPackageFacadesCommand.cs | 62 + .../OrderPackagesAndRollbackBoundariesCommand.cs | 145 ++ src/WixToolset.Core.Burn/Bundles/PackageFacade.cs | 58 + .../Bundles/ProcessExePackageCommand.cs | 33 + .../Bundles/ProcessMsiPackageCommand.cs | 576 ++++++++ .../Bundles/ProcessMspPackageCommand.cs | 189 +++ .../Bundles/ProcessMsuPackageCommand.cs | 30 + .../Bundles/ProcessPayloadsCommand.cs | 161 +++ .../Bundles/VerifyPayloadsWithCatalogCommand.cs | 148 +++ .../Inscribe/InscribeBundleCommand.cs | 53 + .../Inscribe/InscribeBundleEngineCommand.cs | 61 + src/WixToolset.Core.Burn/VerifyInterop.cs | 68 + .../WixToolset.Core.Burn.csproj | 36 + .../Bind/AssignMediaCommand.cs | 314 +++++ .../Bind/BindDatabaseCommand.cs | 1282 ++++++++++++++++++ .../Bind/BindSummaryInfoCommand.cs | 135 ++ .../Bind/BindTransformCommand.cs | 470 +++++++ .../Bind/CabinetBuilder.cs | 177 +++ .../Bind/CabinetResolver.cs | 122 ++ .../Bind/CabinetWorkItem.cs | 79 ++ .../Bind/ConfigurationCallback.cs | 91 ++ .../Bind/CopyTransformDataCommand.cs | 606 +++++++++ .../Bind/CreateCabinetsCommand.cs | 499 +++++++ .../Bind/CreateDeltaPatchesCommand.cs | 87 ++ .../Bind/CreateSpecialPropertiesCommand.cs | 68 + .../Bind/ExtractMergeModuleFilesCommand.cs | 226 ++++ .../Bind/GenerateDatabaseCommand.cs | 332 +++++ .../Bind/GetFileFacadesCommand.cs | 149 +++ .../Bind/MergeModulesCommand.cs | 351 +++++ .../Bind/ProcessUncompressedFilesCommand.cs | 118 ++ .../Bind/UpdateControlTextCommand.cs | 80 ++ .../Bind/UpdateFileFacadesCommand.cs | 533 ++++++++ .../CLR/Interop/CLRInterop.cs | 147 ++ src/WixToolset.Core.WindowsInstaller/Differ.cs | 611 +++++++++ .../Inscribe/InscribeMsiPackageCommand.cs | 282 ++++ .../MergeMod/NativeMethods.cs | 508 +++++++ .../Msi/Database.cs | 303 +++++ .../Msi/Installer.cs | 363 +++++ .../Msi/Interop/MsiInterop.cs | 697 ++++++++++ .../Msi/MsiException.cs | 78 ++ .../Msi/MsiHandle.cs | 116 ++ src/WixToolset.Core.WindowsInstaller/Msi/Record.cs | 182 +++ .../Msi/Session.cs | 45 + .../Msi/SummaryInformation.cs | 245 ++++ src/WixToolset.Core.WindowsInstaller/Msi/View.cs | 189 +++ src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 36 + src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 34 + src/WixToolset.Core.WindowsInstaller/MspBackend.cs | 111 ++ src/WixToolset.Core.WindowsInstaller/MstBackend.cs | 37 + .../Ole32/Storage.cs | 437 ++++++ src/WixToolset.Core.WindowsInstaller/Patch.cs | 1245 +++++++++++++++++ .../PatchAPI/PatchInterop.cs | 989 ++++++++++++++ .../Unbind/ExtractCabinetsCommand.cs | 146 ++ .../Unbind/UnbindDatabaseCommand.cs | 791 +++++++++++ .../Unbind/UnbindMsiOrMsmCommand.cs | 53 + .../Unbind/UnbindTranformCommand.cs | 317 +++++ src/WixToolset.Core.WindowsInstaller/Validator.cs | 385 ++++++ .../ValidatorExtension.cs | 299 +++++ .../WindowsInstallerBackendFactory.cs | 50 + .../WixToolset.Core.WindowsInstaller.csproj | 36 + src/WixToolset.Core/BackendContext.cs | 16 + src/WixToolset.Core/Bind/BindBundleCommand.cs | 905 ------------- src/WixToolset.Core/Bind/BindDatabaseCommand.cs | 1311 ------------------ src/WixToolset.Core/Bind/BindTransformCommand.cs | 473 ------- .../AutomaticallySlipstreamPatchesCommand.cs | 112 -- src/WixToolset.Core/Bind/Bundles/BurnCommon.cs | 378 ------ src/WixToolset.Core/Bind/Bundles/BurnReader.cs | 210 --- src/WixToolset.Core/Bind/Bundles/BurnWriter.cs | 239 ---- ...CreateBootstrapperApplicationManifestCommand.cs | 241 ---- .../Bind/Bundles/CreateBurnManifestCommand.cs | 686 ---------- .../Bind/Bundles/CreateContainerCommand.cs | 68 - .../Bind/Bundles/GetPackageFacadesCommand.cs | 62 - .../OrderPackagesAndRollbackBoundariesCommand.cs | 145 -- src/WixToolset.Core/Bind/Bundles/PackageFacade.cs | 58 - .../Bind/Bundles/ProcessExePackageCommand.cs | 33 - .../Bind/Bundles/ProcessMsiPackageCommand.cs | 560 -------- .../Bind/Bundles/ProcessMspPackageCommand.cs | 189 --- .../Bind/Bundles/ProcessMsuPackageCommand.cs | 30 - .../Bind/Bundles/ProcessPayloadsCommand.cs | 159 --- .../Bundles/VerifyPayloadsWithCatalogCommand.cs | 148 --- .../Bind/Databases/AssignMediaCommand.cs | 314 ----- .../Bind/Databases/BindSummaryInfoCommand.cs | 135 -- .../Bind/Databases/CabinetBuilder.cs | 176 --- .../Bind/Databases/CabinetWorkItem.cs | 78 -- .../Bind/Databases/ConfigurationCallback.cs | 91 -- .../Bind/Databases/CopyTransformDataCommand.cs | 606 --------- .../Bind/Databases/CreateCabinetsCommand.cs | 489 ------- .../Bind/Databases/CreateDeltaPatchesCommand.cs | 86 -- .../Databases/CreateSpecialPropertiesCommand.cs | 68 - .../Databases/ExtractMergeModuleFilesCommand.cs | 225 ---- src/WixToolset.Core/Bind/Databases/FileFacade.cs | 44 - .../Bind/Databases/GetFileFacadesCommand.cs | 148 --- .../Bind/Databases/MergeModulesCommand.cs | 350 ----- .../Databases/ProcessUncompressedFilesCommand.cs | 115 -- .../Bind/Databases/UpdateControlTextCommand.cs | 80 -- .../Bind/Databases/UpdateFileFacadesCommand.cs | 532 -------- src/WixToolset.Core/Bind/DelayedField.cs | 13 +- src/WixToolset.Core/Bind/ExpectedExtractFile.cs | 16 + src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 36 +- .../Bind/ExtractEmbeddedFilesCommand.cs | 29 +- src/WixToolset.Core/Bind/FileFacade.cs | 44 + src/WixToolset.Core/Bind/FileResolver.cs | 231 ++++ src/WixToolset.Core/Bind/FileTransfer.cs | 113 -- .../Bind/GenerateDatabaseCommand.cs | 335 ----- .../Bind/ResolveDelayedFieldsCommand.cs | 15 +- src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | 71 +- src/WixToolset.Core/Bind/ResolveResult.cs | 14 + src/WixToolset.Core/Bind/ResolvedDirectory.cs | 2 +- src/WixToolset.Core/Bind/TransferFilesCommand.cs | 61 +- src/WixToolset.Core/BindContext.cs | 57 + src/WixToolset.Core/Binder.cs | 389 ++++-- src/WixToolset.Core/BinderFileManager.cs | 2 + src/WixToolset.Core/BinderFileManagerCore.cs | 1 + src/WixToolset.Core/CLR/Interop/CLRInterop.cs | 147 -- src/WixToolset.Core/Cab/CabinetFileInfo.cs | 39 +- src/WixToolset.Core/Cab/WixCreateCab.cs | 4 +- src/WixToolset.Core/Cab/WixEnumerateCab.cs | 6 +- src/WixToolset.Core/Cab/WixExtractCab.cs | 3 +- src/WixToolset.Core/CommandLine/BuildCommand.cs | 142 +- src/WixToolset.Core/CommandLine/CommandLine.cs | 22 +- src/WixToolset.Core/Common.cs | 162 ++- src/WixToolset.Core/Compiler.cs | 24 +- src/WixToolset.Core/CompilerCore.cs | 9 +- src/WixToolset.Core/Data/messages.xml | 12 - src/WixToolset.Core/Decompiler.cs | 23 +- src/WixToolset.Core/Differ.cs | 621 --------- src/WixToolset.Core/Extensibility/HeatExtension.cs | 4 - src/WixToolset.Core/Extensibility/IHeatCore.cs | 2 +- .../Extensibility/ValidatorExtension.cs | 299 ----- src/WixToolset.Core/ExtensionManager.cs | 6 +- src/WixToolset.Core/HeatCore.cs | 5 +- src/WixToolset.Core/IncribeContext.cs | 20 + src/WixToolset.Core/Inscriber.cs | 641 +++++---- src/WixToolset.Core/Librarian.cs | 43 +- src/WixToolset.Core/LibraryContext.cs | 23 + src/WixToolset.Core/Linker.cs | 2 +- src/WixToolset.Core/Localizer.cs | 51 +- src/WixToolset.Core/MergeMod/NativeMethods.cs | 508 ------- src/WixToolset.Core/Msi/Database.cs | 303 ----- src/WixToolset.Core/Msi/Installer.cs | 484 ------- src/WixToolset.Core/Msi/Interop/MsiInterop.cs | 697 ---------- src/WixToolset.Core/Msi/MsiException.cs | 78 -- src/WixToolset.Core/Msi/MsiHandle.cs | 116 -- src/WixToolset.Core/Msi/Record.cs | 182 --- src/WixToolset.Core/Msi/Session.cs | 45 - src/WixToolset.Core/Msi/SummaryInformation.cs | 323 ----- src/WixToolset.Core/Msi/View.cs | 189 --- src/WixToolset.Core/Ole32/Storage.cs | 437 ------ src/WixToolset.Core/OptimizeCA.cs | 33 + src/WixToolset.Core/Patch.cs | 1284 ------------------ src/WixToolset.Core/PatchAPI/PatchInterop.cs | 1002 -------------- src/WixToolset.Core/PatchSymbolFlagsType.cs | 19 + src/WixToolset.Core/PatchTransform.cs | 4 +- src/WixToolset.Core/Properties/AssemblyInfo.cs | 2 +- src/WixToolset.Core/ProvidesDependency.cs | 108 -- .../ProvidesDependencyCollection.cs | 64 - src/WixToolset.Core/TransformsFlags.cs | 81 ++ src/WixToolset.Core/UnbindContext.cs | 24 + src/WixToolset.Core/Unbinder.cs | 1399 +------------------- src/WixToolset.Core/Uuid.cs | 2 +- src/WixToolset.Core/Validator.cs | 401 ------ src/WixToolset.Core/VerifyInterop.cs | 68 - src/WixToolset.Core/WixComponentSearchInfo.cs | 64 - src/WixToolset.Core/WixFileSearchInfo.cs | 54 - src/WixToolset.Core/WixProductSearchInfo.cs | 67 - src/WixToolset.Core/WixRegistrySearchInfo.cs | 92 -- src/WixToolset.Core/WixSearchInfo.cs | 53 - src/WixToolset.Core/WixStrings.Designer.cs | 10 +- src/WixToolset.Core/WixVariableResolver.cs | 43 +- src/wix/wix.csproj | 2 + 189 files changed, 21276 insertions(+), 19868 deletions(-) create mode 100644 src/WixToolset.Core.Burn/BackendFactory.cs create mode 100644 src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs create mode 100644 src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs create mode 100644 src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs create mode 100644 src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs create mode 100644 src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs create mode 100644 src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs create mode 100644 src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs create mode 100644 src/WixToolset.Core.Burn/BundleBackend.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/BurnCommon.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/BurnReader.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/BurnWriter.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/PackageFacade.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs create mode 100644 src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs create mode 100644 src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs create mode 100644 src/WixToolset.Core.Burn/VerifyInterop.cs create mode 100644 src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Differ.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/MergeMod/NativeMethods.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/Database.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/Installer.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/Interop/MsiInterop.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/MsiException.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/MsiHandle.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/Record.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/Session.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/SummaryInformation.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/View.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/MsiBackend.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/MsmBackend.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/MspBackend.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/MstBackend.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Patch.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Validator.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj create mode 100644 src/WixToolset.Core/BackendContext.cs delete mode 100644 src/WixToolset.Core/Bind/BindBundleCommand.cs delete mode 100644 src/WixToolset.Core/Bind/BindDatabaseCommand.cs delete mode 100644 src/WixToolset.Core/Bind/BindTransformCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/BurnCommon.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/BurnReader.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/BurnWriter.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/PackageFacade.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/FileFacade.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs delete mode 100644 src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs create mode 100644 src/WixToolset.Core/Bind/ExpectedExtractFile.cs create mode 100644 src/WixToolset.Core/Bind/FileFacade.cs create mode 100644 src/WixToolset.Core/Bind/FileResolver.cs delete mode 100644 src/WixToolset.Core/Bind/FileTransfer.cs delete mode 100644 src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs create mode 100644 src/WixToolset.Core/Bind/ResolveResult.cs create mode 100644 src/WixToolset.Core/BindContext.cs delete mode 100644 src/WixToolset.Core/CLR/Interop/CLRInterop.cs delete mode 100644 src/WixToolset.Core/Differ.cs delete mode 100644 src/WixToolset.Core/Extensibility/ValidatorExtension.cs create mode 100644 src/WixToolset.Core/IncribeContext.cs create mode 100644 src/WixToolset.Core/LibraryContext.cs delete mode 100644 src/WixToolset.Core/MergeMod/NativeMethods.cs delete mode 100644 src/WixToolset.Core/Msi/Database.cs delete mode 100644 src/WixToolset.Core/Msi/Installer.cs delete mode 100644 src/WixToolset.Core/Msi/Interop/MsiInterop.cs delete mode 100644 src/WixToolset.Core/Msi/MsiException.cs delete mode 100644 src/WixToolset.Core/Msi/MsiHandle.cs delete mode 100644 src/WixToolset.Core/Msi/Record.cs delete mode 100644 src/WixToolset.Core/Msi/Session.cs delete mode 100644 src/WixToolset.Core/Msi/SummaryInformation.cs delete mode 100644 src/WixToolset.Core/Msi/View.cs delete mode 100644 src/WixToolset.Core/Ole32/Storage.cs create mode 100644 src/WixToolset.Core/OptimizeCA.cs delete mode 100644 src/WixToolset.Core/Patch.cs delete mode 100644 src/WixToolset.Core/PatchAPI/PatchInterop.cs create mode 100644 src/WixToolset.Core/PatchSymbolFlagsType.cs delete mode 100644 src/WixToolset.Core/ProvidesDependency.cs delete mode 100644 src/WixToolset.Core/ProvidesDependencyCollection.cs create mode 100644 src/WixToolset.Core/TransformsFlags.cs create mode 100644 src/WixToolset.Core/UnbindContext.cs delete mode 100644 src/WixToolset.Core/Validator.cs delete mode 100644 src/WixToolset.Core/VerifyInterop.cs delete mode 100644 src/WixToolset.Core/WixComponentSearchInfo.cs delete mode 100644 src/WixToolset.Core/WixFileSearchInfo.cs delete mode 100644 src/WixToolset.Core/WixProductSearchInfo.cs delete mode 100644 src/WixToolset.Core/WixRegistrySearchInfo.cs delete mode 100644 src/WixToolset.Core/WixSearchInfo.cs diff --git a/WixToolset.Core.sln b/WixToolset.Core.sln index b94c31b6..8771dc4c 100644 --- a/WixToolset.Core.sln +++ b/WixToolset.Core.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 +VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core", "src\WixToolset.Core\WixToolset.Core.csproj", "{0B524850-5B9A-472B-85CC-D25920A1DFE1}" EndProject @@ -8,24 +8,80 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "wix", "src\wix\wix.csproj", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.BuildTasks", "src\WixToolset.BuildTasks\WixToolset.BuildTasks.csproj", "{C199C723-73E1-4E79-AF86-898DBA007FC3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.WindowsInstaller", "src\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj", "{5617F2A7-46A0-4D07-B9E0-E982D15641E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.Burn", "src\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj", "{BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.ActiveCfg = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.Build.0 = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.ActiveCfg = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.Build.0 = Debug|Any CPU {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.Build.0 = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.ActiveCfg = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.Build.0 = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.ActiveCfg = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.Build.0 = Release|Any CPU {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|x64.ActiveCfg = Debug|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|x64.Build.0 = Debug|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|x86.ActiveCfg = Debug|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|x86.Build.0 = Debug|Any CPU {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|Any CPU.ActiveCfg = Release|Any CPU {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|Any CPU.Build.0 = Release|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|x64.ActiveCfg = Release|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|x64.Build.0 = Release|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|x86.ActiveCfg = Release|Any CPU + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|x86.Build.0 = Release|Any CPU {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|x64.ActiveCfg = Debug|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|x64.Build.0 = Debug|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|x86.ActiveCfg = Debug|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|x86.Build.0 = Debug|Any CPU {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|Any CPU.Build.0 = Release|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|x64.ActiveCfg = Release|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|x64.Build.0 = Release|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|x86.ActiveCfg = Release|Any CPU + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|x86.Build.0 = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.ActiveCfg = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.Build.0 = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.ActiveCfg = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.Build.0 = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.Build.0 = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.ActiveCfg = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.Build.0 = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.ActiveCfg = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.Build.0 = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.ActiveCfg = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.Build.0 = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.ActiveCfg = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.Build.0 = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.Build.0 = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.ActiveCfg = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.Build.0 = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.ActiveCfg = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj index 10dce157..c34b3bca 100644 --- a/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj +++ b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj @@ -24,6 +24,8 @@ + + diff --git a/src/WixToolset.Core.Burn/BackendFactory.cs b/src/WixToolset.Core.Burn/BackendFactory.cs new file mode 100644 index 00000000..042fa254 --- /dev/null +++ b/src/WixToolset.Core.Burn/BackendFactory.cs @@ -0,0 +1,30 @@ +// 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.Burn +{ + using System; + using System.IO; + using WixToolset.Extensibility; + + internal class BackendFactory : IBackendFactory + { + public bool TryCreateBackend(string outputType, string outputFile, IBindContext context, out IBackend backend) + { + if (String.IsNullOrEmpty(outputType)) + { + outputType = Path.GetExtension(outputFile); + } + + switch (outputType.ToLowerInvariant()) + { + case "bundle": + case ".exe": + backend = new BundleBackend(); + return true; + } + + backend = null; + return false; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs new file mode 100644 index 00000000..212b1e81 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -0,0 +1,942 @@ +// 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.Burn +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Reflection; + using WixToolset.Bind; + using WixToolset.Core.Bind; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + + // TODO: (4.0) Refactor so that these don't need to be copied. + // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs + [Flags] + internal enum WixFileSearchAttributes + { + Default = 0x001, + MinVersionInclusive = 0x002, + MaxVersionInclusive = 0x004, + MinSizeInclusive = 0x008, + MaxSizeInclusive = 0x010, + MinDateInclusive = 0x020, + MaxDateInclusive = 0x040, + WantVersion = 0x080, + WantExists = 0x100, + IsDirectory = 0x200, + } + + [Flags] + internal enum WixRegistrySearchAttributes + { + Raw = 0x01, + Compatible = 0x02, + ExpandEnvironmentVariables = 0x04, + WantValue = 0x08, + WantExists = 0x10, + Win64 = 0x20, + } + + internal enum WixComponentSearchAttributes + { + KeyPath = 0x1, + State = 0x2, + WantDirectory = 0x4, + } + + [Flags] + internal enum WixProductSearchAttributes + { + Version = 0x1, + Language = 0x2, + State = 0x4, + Assignment = 0x8, + UpgradeCode = 0x10, + } + + /// + /// Binds a this.bundle. + /// + internal class BindBundleCommand + { + public BindBundleCommand(IBindContext context) + { + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + + this.DelayedFields = context.DelayedFields; + this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + + this.BackendExtensions = context.ExtensionManager.Create(); + } + + public CompressionLevel DefaultCompressionLevel { private get; set; } + + public IEnumerable DelayedFields { get; } + + public IEnumerable ExpectedEmbeddedFiles { get; } + + private IEnumerable BackendExtensions { get; } + + public IEnumerable Extensions { private get; set; } + + public Output Output { private get; set; } + + public string OutputPath { private get; set; } + + public string PdbFile { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public string IntermediateFolder { private get; set; } + + public IBindVariableResolver WixVariableResolver { private get; set; } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable ContentFilePaths { get; private set; } + + public void Execute() + { + this.FileTransfers = Enumerable.Empty(); + this.ContentFilePaths = Enumerable.Empty(); + + // First look for data we expect to find... Chain, WixGroups, etc. + + // We shouldn't really get past the linker phase if there are + // no group items... that means that there's no UX, no Chain, + // *and* no Containers! + Table chainPackageTable = this.GetRequiredTable("WixBundlePackage"); + + Table wixGroupTable = this.GetRequiredTable("WixGroup"); + + // Ensure there is one and only one row in the WixBundle table. + // The compiler and linker behavior should have colluded to get + // this behavior. + WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle"); + + bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user. + + // Ensure there is one and only one row in the WixBootstrapperApplication table. + // The compiler and linker behavior should have colluded to get + // this behavior. + Row baRow = this.GetSingleRowTable("WixBootstrapperApplication"); + + // Ensure there is one and only one row in the WixChain table. + // The compiler and linker behavior should have colluded to get + // this behavior. + WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain"); + + if (Messaging.Instance.EncounteredError) + { + return; + } + + // If there are any fields to resolve later, create the cache to populate during bind. + IDictionary variableCache = null; + if (this.DelayedFields.Any()) + { + variableCache = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + } + + // TODO: Although the WixSearch tables are defined in the Util extension, + // the Bundle Binder has to know all about them. We hope to revisit all + // of this in the 4.0 timeframe. + IEnumerable orderedSearches = this.OrderSearches(); + + // Extract files that come from cabinet files (this does not extract files from merge modules). + { + var extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand(); + extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = ExpectedEmbeddedFiles; + extractEmbeddedFilesCommand.Execute(); + } + + // Get the explicit payloads. + RowDictionary payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); + + // Update explicitly authored payloads with their parent package and container (as appropriate) + // to make it easier to gather the payloads later. + foreach (WixGroupRow row in wixGroupTable.RowsAs()) + { + if (ComplexReferenceChildType.Payload == row.ChildType) + { + WixBundlePayloadRow payload = payloads.Get(row.ChildId); + + if (ComplexReferenceParentType.Package == row.ParentType) + { + Debug.Assert(String.IsNullOrEmpty(payload.Package)); + payload.Package = row.ParentId; + } + else if (ComplexReferenceParentType.Container == row.ParentType) + { + Debug.Assert(String.IsNullOrEmpty(payload.Container)); + payload.Container = row.ParentId; + } + else if (ComplexReferenceParentType.Layout == row.ParentType) + { + payload.LayoutOnly = true; + } + } + } + + List fileTransfers = new List(); + string layoutDirectory = Path.GetDirectoryName(this.OutputPath); + + // Process the explicitly authored payloads. + ISet processedPayloads; + { + ProcessPayloadsCommand command = new ProcessPayloadsCommand(); + command.Payloads = payloads.Values; + command.DefaultPackaging = bundleRow.DefaultPackagingType; + command.LayoutDirectory = layoutDirectory; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + + processedPayloads = new HashSet(payloads.Keys); + } + + IDictionary facades; + { + GetPackageFacadesCommand command = new GetPackageFacadesCommand(); + command.PackageTable = chainPackageTable; + command.ExePackageTable = this.Output.Tables["WixBundleExePackage"]; + command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"]; + command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"]; + command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"]; + command.Execute(); + + facades = command.PackageFacades; + } + + // Process each package facade. Note this is likely to add payloads and other rows to tables so + // note that any indexes created above may be out of date now. + foreach (PackageFacade facade in facades.Values) + { + switch (facade.Package.Type) + { + case WixBundlePackageType.Exe: + { + ProcessExePackageCommand command = new ProcessExePackageCommand(); + command.AuthoredPayloads = payloads; + command.Facade = facade; + command.Execute(); + + // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer); + } + break; + + case WixBundlePackageType.Msi: + { + var command = new ProcessMsiPackageCommand(); + command.AuthoredPayloads = payloads; + command.Facade = facade; + command.BackendExtensions = this.BackendExtensions; + command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]); + command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]); + command.PayloadTable = this.Output.Tables["WixBundlePayload"]; + command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]); + command.Execute(); + + if (null != variableCache) + { + variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString()); + + if (null != facade.MsiPackage.Manufacturer) + { + variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer); + } + } + + } + break; + + case WixBundlePackageType.Msp: + { + ProcessMspPackageCommand command = new ProcessMspPackageCommand(); + command.AuthoredPayloads = payloads; + command.Facade = facade; + command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); + command.Execute(); + } + break; + + case WixBundlePackageType.Msu: + { + ProcessMsuPackageCommand command = new ProcessMsuPackageCommand(); + command.Facade = facade; + command.Execute(); + } + break; + } + + if (null != variableCache) + { + BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache); + } + } + + // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) + // are present. + payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); + + // Process the payloads that were added by processing the packages. + { + ProcessPayloadsCommand command = new ProcessPayloadsCommand(); + command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList(); + command.DefaultPackaging = bundleRow.DefaultPackagingType; + command.LayoutDirectory = layoutDirectory; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + + processedPayloads = null; + } + + // Set the package metadata from the payloads now that we have the complete payload information. + ILookup payloadsByPackage = payloads.Values.ToLookup(p => p.Package); + + { + foreach (PackageFacade facade in facades.Values) + { + facade.Package.Size = 0; + + IEnumerable packagePayloads = payloadsByPackage[facade.Package.WixChainItemId]; + + foreach (WixBundlePayloadRow payload in packagePayloads) + { + facade.Package.Size += payload.FileSize; + } + + if (!facade.Package.InstallSize.HasValue) + { + facade.Package.InstallSize = facade.Package.Size; + + } + + WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload]; + + if (String.IsNullOrEmpty(facade.Package.Description)) + { + facade.Package.Description = packagePayload.Description; + } + + if (String.IsNullOrEmpty(facade.Package.DisplayName)) + { + facade.Package.DisplayName = packagePayload.DisplayName; + } + } + } + + + // Give the UX payloads their embedded IDs... + int uxPayloadIndex = 0; + { + foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container)) + { + // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even + // downloaded. The current engine requires the UX to be fully present before any downloading starts, + // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads + // into the temporary UX directory correctly, so we don't allow external either. + if (PackagingType.Embedded != payload.Packaging) + { + Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); + payload.Packaging = PackagingType.Embedded; + } + + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); + ++uxPayloadIndex; + } + + if (0 == uxPayloadIndex) + { + // If we didn't get any UX payloads, it's an error! + throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); + } + + // Give the embedded payloads without an embedded id yet an embedded id. + int payloadIndex = 0; + foreach (WixBundlePayloadRow payload in payloads.Values) + { + Debug.Assert(PackagingType.Unknown != payload.Packaging); + + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) + { + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex); + ++payloadIndex; + } + } + } + + // Determine patches to automatically slipstream. + { + AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand(); + command.PackageFacades = facades.Values; + command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]); + command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); + command.Execute(); + } + + // If catalog files exist, non-embedded payloads should validate with the catalogs. + IEnumerable catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs(); + + if (catalogs.Any()) + { + VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand(); + command.Catalogs = catalogs; + command.Payloads = payloads.Values; + command.Execute(); + } + + if (Messaging.Instance.EncounteredError) + { + return; + } + + IEnumerable orderedFacades; + IEnumerable boundaries; + { + OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand(); + command.Boundaries = new RowDictionary(this.Output.Tables["WixBundleRollbackBoundary"]); + command.PackageFacades = facades; + command.WixGroupTable = wixGroupTable; + command.Execute(); + + orderedFacades = command.OrderedPackageFacades; + boundaries = command.UsedRollbackBoundaries; + } + + // Resolve any delayed fields before generating the manifest. + if (this.DelayedFields.Any()) + { + var resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(); + resolveDelayedFieldsCommand.OutputType = this.Output.Type; + resolveDelayedFieldsCommand.DelayedFields = this.DelayedFields; + resolveDelayedFieldsCommand.ModularizationGuid = null; + resolveDelayedFieldsCommand.VariableCache = variableCache; + resolveDelayedFieldsCommand.Execute(); + } + + // Set the overridable bundle provider key. + this.SetBundleProviderKey(this.Output, bundleRow); + + // Import or generate dependency providers for packages in the manifest. + this.ProcessDependencyProviders(this.Output, facades); + + // Update the bundle per-machine/per-user scope based on the chained packages. + this.ResolveBundleInstallScope(bundleRow, orderedFacades); + + // Generate the core-defined BA manifest tables... + { + CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand(); + command.BundleRow = bundleRow; + command.ChainPackages = orderedFacades; + command.LastUXPayloadIndex = uxPayloadIndex; + command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs(); + command.Output = this.Output; + command.Payloads = payloads; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.Execute(); + + WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; + payloads.Add(baManifestPayload); + } + + //foreach (BinderExtension extension in this.Extensions) + //{ + // extension.PostBind(this.Context); + //} + + // Create all the containers except the UX container first so the manifest (that goes in the UX container) + // can contain all size and hash information about the non-UX containers. + RowDictionary containers = new RowDictionary(this.Output.Tables["WixBundleContainer"]); + + ILookup payloadsByContainer = payloads.Values.ToLookup(p => p.Container); + + int attachedContainerIndex = 1; // count starts at one because UX container is "0". + + IEnumerable uxContainerPayloads = Enumerable.Empty(); + + foreach (WixBundleContainerRow container in containers.Values) + { + IEnumerable containerPayloads = payloadsByContainer[container.Id]; + + if (!containerPayloads.Any()) + { + if (container.Id != Compiler.BurnDefaultAttachedContainerId) + { + // TODO: display warning that we're ignoring container that ended up with no paylods in it. + } + } + else if (Compiler.BurnUXContainerId == container.Id) + { + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + container.AttachedContainerIndex = 0; + + // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first + // in the list since that is the Payload that Burn attempts to load. + List uxPayloads = new List(); + + string baPayloadId = baRow.FieldAsString(0); + + foreach (WixBundlePayloadRow uxPayload in containerPayloads) + { + if (uxPayload.Id == baPayloadId) + { + uxPayloads.Insert(0, uxPayload); + } + else + { + uxPayloads.Add(uxPayload); + } + } + + uxContainerPayloads = uxPayloads; + } + else + { + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + + // Add detached containers to the list of file transfers. + if (ContainerType.Detached == container.Type) + { + FileTransfer transfer; + if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) + { + transfer.Built = true; + fileTransfers.Add(transfer); + } + } + else // update the attached container index. + { + Debug.Assert(ContainerType.Attached == container.Type); + + container.AttachedContainerIndex = attachedContainerIndex; + ++attachedContainerIndex; + } + + this.CreateContainer(container, containerPayloads, null); + } + } + + // Create the bundle manifest then UX container. + string manifestPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); + { + var command = new CreateBurnManifestCommand(); + command.BackendExtensions = this.BackendExtensions; + command.Output = this.Output; + + command.BundleInfo = bundleRow; + command.Chain = chainRow; + command.Containers = containers; + command.Catalogs = catalogs; + command.ExecutableName = Path.GetFileName(this.OutputPath); + command.OrderedPackages = orderedFacades; + command.OutputPath = manifestPath; + command.RollbackBoundaries = boundaries; + command.OrderedSearches = orderedSearches; + command.Payloads = payloads; + command.UXContainerPayloads = uxContainerPayloads; + command.Execute(); + } + + WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId]; + this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath); + + // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note + // that today, the x64 Burn uses the x86 stub. + string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString(); + + string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); + string bundleTempPath = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); + + Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile)); + + string bundleFilename = Path.GetFileName(this.OutputPath); + if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) + { + Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename)); + } + + FileTransfer bundleTransfer; + if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer)) + { + bundleTransfer.Built = true; + fileTransfers.Add(bundleTransfer); + } + + File.Copy(stubFile, bundleTempPath, true); + File.SetAttributes(bundleTempPath, FileAttributes.Normal); + + this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow); + + // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers + // if they should be attached. + using (BurnWriter writer = BurnWriter.Open(bundleTempPath)) + { + FileInfo burnStubFile = new FileInfo(bundleTempPath); + writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId); + + // Always attach the UX container first + writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX); + + // Now append all other attached containers + foreach (WixBundleContainerRow container in containers.Values) + { + if (ContainerType.Attached == container.Type) + { + // The container was only created if it had payloads. + if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) + { + writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); + } + } + } + } + + if (null != this.PdbFile) + { + Pdb pdb = new Pdb(); + pdb.Output = Output; + pdb.Save(this.PdbFile); + } + + this.FileTransfers = fileTransfers; + this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); + } + + private Table GetRequiredTable(string tableName) + { + Table table = this.Output.Tables[tableName]; + if (null == table || 0 == table.Rows.Count) + { + throw new WixException(WixErrors.MissingBundleInformation(tableName)); + } + + return table; + } + + private Row GetSingleRowTable(string tableName) + { + Table table = this.Output.Tables[tableName]; + if (null == table || 1 != table.Rows.Count) + { + throw new WixException(WixErrors.MissingBundleInformation(tableName)); + } + + return table.Rows[0]; + } + + private List OrderSearches() + { + Dictionary allSearches = new Dictionary(); + Table wixFileSearchTable = this.Output.Tables["WixFileSearch"]; + if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) + { + foreach (Row row in wixFileSearchTable.Rows) + { + WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row); + allSearches.Add(fileSearchInfo.Id, fileSearchInfo); + } + } + + Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"]; + if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count) + { + foreach (Row row in wixRegistrySearchTable.Rows) + { + WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row); + allSearches.Add(registrySearchInfo.Id, registrySearchInfo); + } + } + + Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"]; + if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count) + { + foreach (Row row in wixComponentSearchTable.Rows) + { + WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row); + allSearches.Add(componentSearchInfo.Id, componentSearchInfo); + } + } + + Table wixProductSearchTable = this.Output.Tables["WixProductSearch"]; + if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count) + { + foreach (Row row in wixProductSearchTable.Rows) + { + WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row); + allSearches.Add(productSearchInfo.Id, productSearchInfo); + } + } + + // Merge in the variable/condition info and get the canonical ordering for + // the searches. + List orderedSearches = new List(); + Table wixSearchTable = this.Output.Tables["WixSearch"]; + if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count) + { + orderedSearches.Capacity = wixSearchTable.Rows.Count; + foreach (Row row in wixSearchTable.Rows) + { + WixSearchInfo searchInfo = allSearches[(string)row[0]]; + searchInfo.AddWixSearchRowInfo(row); + orderedSearches.Add(searchInfo); + } + } + + return orderedSearches; + } + + /// + /// Populates the variable cache with specific package properties. + /// + /// The package with properties to cache. + /// The property cache. + private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary variableCache) + { + string id = package.WixChainItemId; + + variableCache.Add(String.Concat("packageDescription.", id), package.Description); + //variableCache.Add(String.Concat("packageLanguage.", id), package.Language); + //variableCache.Add(String.Concat("packageManufacturer.", id), package.Manufacturer); + variableCache.Add(String.Concat("packageName.", id), package.DisplayName); + variableCache.Add(String.Concat("packageVersion.", id), package.Version); + } + + private void CreateContainer(WixBundleContainerRow container, IEnumerable containerPayloads, string manifestFile) + { + CreateContainerCommand command = new CreateContainerCommand(); + command.DefaultCompressionLevel = this.DefaultCompressionLevel; + command.Payloads = containerPayloads; + command.ManifestFile = manifestFile; + command.OutputPath = container.WorkingPath; + command.Execute(); + + container.Hash = command.Hash; + container.Size = command.Size; + } + + private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable facades) + { + foreach (PackageFacade facade in facades) + { + if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine) + { + Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); + + bundleInfo.PerMachine = false; + break; + } + } + + foreach (PackageFacade facade in facades) + { + // Update package scope from bundle scope if default. + if (YesNoDefaultType.Default == facade.Package.PerMachine) + { + facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; + } + + // We will only register packages in the same scope as the bundle. Warn if any packages with providers + // are in a different scope and not permanent (permanents typically don't need a ref-count). + if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count) + { + Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); + } + } + } + + private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo) + { + WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection(); + WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033); + + version.Load(bundleTempPath); + resources.Add(version); + + // Ensure the bundle info provides a full four part version. + Version fourPartVersion = new Version(bundleInfo.Version); + int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; + int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; + int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; + int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; + + if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) + { + throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version)); + } + + fourPartVersion = new Version(major, minor, build, revision); + version.FileVersion = fourPartVersion; + version.ProductVersion = fourPartVersion; + + WixToolset.Dtf.Resources.VersionStringTable strings = version[1033]; + strings["LegalCopyright"] = bundleInfo.Copyright; + strings["OriginalFilename"] = Path.GetFileName(outputPath); + strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + + if (!String.IsNullOrEmpty(bundleInfo.Name)) + { + strings["ProductName"] = bundleInfo.Name; + strings["FileDescription"] = bundleInfo.Name; + } + + if (!String.IsNullOrEmpty(bundleInfo.Publisher)) + { + strings["CompanyName"] = bundleInfo.Publisher; + } + else + { + strings["CompanyName"] = String.Empty; + } + + if (!String.IsNullOrEmpty(bundleInfo.IconPath)) + { + Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033); + iconGroup.ReadFromFile(bundleInfo.IconPath); + resources.Add(iconGroup); + + foreach (Dtf.Resources.Resource icon in iconGroup.Icons) + { + resources.Add(icon); + } + } + + if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath)) + { + Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033); + bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath); + resources.Add(bitmap); + } + + resources.Save(bundleTempPath); + } + + #region DependencyExtension + /// + /// Imports authored dependency providers for each package in the manifest, + /// and generates dependency providers for certain package types that do not + /// have a provider defined. + /// + /// The object for the bundle. + /// An indexed collection of chained packages. + private void ProcessDependencyProviders(Output bundle, IDictionary facades) + { + // First import any authored dependencies. These may merge with imported provides from MSI packages. + Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; + if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) + { + // Add package information for each dependency provider authored into the manifest. + foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) + { + string packageId = (string)wixDependencyProviderRow[1]; + + PackageFacade facade = null; + if (facades.TryGetValue(packageId, out facade)) + { + ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow); + + if (String.IsNullOrEmpty(dependency.Key)) + { + switch (facade.Package.Type) + { + // The WixDependencyExtension allows an empty Key for MSIs and MSPs. + case WixBundlePackageType.Msi: + dependency.Key = facade.MsiPackage.ProductCode; + break; + case WixBundlePackageType.Msp: + dependency.Key = facade.MspPackage.PatchCode; + break; + } + } + + if (String.IsNullOrEmpty(dependency.Version)) + { + dependency.Version = facade.Package.Version; + } + + // If the version is still missing, a version could not be harvested from the package and was not authored. + if (String.IsNullOrEmpty(dependency.Version)) + { + Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId)); + } + + if (String.IsNullOrEmpty(dependency.DisplayName)) + { + dependency.DisplayName = facade.Package.DisplayName; + } + + if (!facade.Provides.Merge(dependency)) + { + Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); + } + } + } + } + + // Generate providers for MSI packages that still do not have providers. + foreach (PackageFacade facade in facades.Values) + { + string key = null; + + if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count) + { + key = facade.MsiPackage.ProductCode; + } + else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count) + { + key = facade.MspPackage.PatchCode; + } + + if (!String.IsNullOrEmpty(key)) + { + ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0); + + if (!facade.Provides.Merge(dependency)) + { + Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); + } + } + } + } + + /// + /// Sets the provider key for the bundle. + /// + /// The object for the bundle. + /// The containing the provider key and other information for the bundle. + private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo) + { + // From DependencyCommon.cs in the WixDependencyExtension. + const int ProvidesAttributesBundle = 0x10000; + + Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; + if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) + { + // Search the WixDependencyProvider table for the single bundle provider key. + foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) + { + object attributes = wixDependencyProviderRow[5]; + if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes)) + { + bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2]; + break; + } + } + } + + // Defaults to the bundle ID as the provider key. + } + #endregion + } +} diff --git a/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs b/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs new file mode 100644 index 00000000..e64773b4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs @@ -0,0 +1,108 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + + /// + /// Represents an authored or imported dependency provider. + /// + internal sealed class ProvidesDependency + { + /// + /// Creates a new instance of the class from a . + /// + /// The from which data is imported. + internal ProvidesDependency(Row row) + : this((string)row[2], (string)row[3], (string)row[4], (int?)row[5]) + { + } + + /// + /// Creates a new instance of the class. + /// + /// The unique key of the dependency. + /// Additional attributes for the dependency. + internal ProvidesDependency(string key, string version, string displayName, int? attributes) + { + this.Key = key; + this.Version = version; + this.DisplayName = displayName; + this.Attributes = attributes; + } + + /// + /// Gets or sets the unique key of the package provider. + /// + internal string Key { get; set; } + + /// + /// Gets or sets the version of the package provider. + /// + internal string Version { get; set; } + + /// + /// Gets or sets the display name of the package provider. + /// + internal string DisplayName { get; set; } + + /// + /// Gets or sets the attributes for the dependency. + /// + internal int? Attributes { get; set; } + + /// + /// Gets or sets whether the dependency was imported from the package. + /// + internal bool Imported { get; set; } + + /// + /// Gets whether certain properties are the same. + /// + /// Another to compare. + /// This is not the same as object equality, but only checks a subset of properties + /// to determine if the objects are similar and could be merged into a collection. + /// True if certain properties are the same. + internal bool Equals(ProvidesDependency other) + { + if (null != other) + { + return this.Key == other.Key && + this.Version == other.Version && + this.DisplayName == other.DisplayName; + } + + return false; + } + + /// + /// Writes the dependency to the bundle XML manifest. + /// + /// The for the bundle XML manifest. + internal void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("Provides"); + writer.WriteAttributeString("Key", this.Key); + + if (!String.IsNullOrEmpty(this.Version)) + { + writer.WriteAttributeString("Version", this.Version); + } + + if (!String.IsNullOrEmpty(this.DisplayName)) + { + writer.WriteAttributeString("DisplayName", this.DisplayName); + } + + if (this.Imported) + { + // The package dependency was explicitly authored into the manifest. + writer.WriteAttributeString("Imported", "yes"); + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs b/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs new file mode 100644 index 00000000..668b81d3 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs @@ -0,0 +1,64 @@ +// 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.Burn +{ + using System; + using System.Collections.ObjectModel; + + /// + /// A case-insensitive collection of unique objects. + /// + internal sealed class ProvidesDependencyCollection : KeyedCollection + { + /// + /// Creates a case-insensitive collection of unique objects. + /// + internal ProvidesDependencyCollection() + : base(StringComparer.InvariantCultureIgnoreCase) + { + } + + /// + /// Adds the to the collection if it doesn't already exist. + /// + /// The to add to the collection. + /// True if the was added to the collection; otherwise, false. + /// The parameter is null. + internal bool Merge(ProvidesDependency dependency) + { + if (null == dependency) + { + throw new ArgumentNullException("dependency"); + } + + // If the dependency key is already in the collection, verify equality for a subset of properties. + if (this.Contains(dependency.Key)) + { + ProvidesDependency current = this[dependency.Key]; + if (!current.Equals(dependency)) + { + return false; + } + } + + base.Add(dependency); + return true; + } + + /// + /// Gets the for the . + /// + /// The dependency to index. + /// The parameter is null. + /// The for the . + protected override string GetKeyForItem(ProvidesDependency dependency) + { + if (null == dependency) + { + throw new ArgumentNullException("dependency"); + } + + return dependency.Key; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs new file mode 100644 index 00000000..f605d7c7 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs @@ -0,0 +1,64 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + + /// + /// Utility class for all WixComponentSearches. + /// + internal class WixComponentSearchInfo : WixSearchInfo + { + public WixComponentSearchInfo(Row row) + : this((string)row[0], (string)row[1], (string)row[2], (int)row[3]) + { + } + + public WixComponentSearchInfo(string id, string guid, string productCode, int attributes) + : base(id) + { + this.Guid = guid; + this.ProductCode = productCode; + this.Attributes = (WixComponentSearchAttributes)attributes; + } + + public string Guid { get; private set; } + public string ProductCode { get; private set; } + public WixComponentSearchAttributes Attributes { get; private set; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup for a component search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("MsiComponentSearch"); + this.WriteWixSearchAttributes(writer); + + writer.WriteAttributeString("ComponentId", this.Guid); + + if (!String.IsNullOrEmpty(this.ProductCode)) + { + writer.WriteAttributeString("ProductCode", this.ProductCode); + } + + if (0 != (this.Attributes & WixComponentSearchAttributes.KeyPath)) + { + writer.WriteAttributeString("Type", "keyPath"); + } + else if (0 != (this.Attributes & WixComponentSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (this.Attributes & WixComponentSearchAttributes.WantDirectory)) + { + writer.WriteAttributeString("Type", "directory"); + } + + writer.WriteEndElement(); + } + } + +} diff --git a/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs new file mode 100644 index 00000000..ea955db4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs @@ -0,0 +1,54 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + + /// + /// Utility class for all WixFileSearches (file and directory searches). + /// + internal class WixFileSearchInfo : WixSearchInfo + { + public WixFileSearchInfo(Row row) + : this((string)row[0], (string)row[1], (int)row[9]) + { + } + + public WixFileSearchInfo(string id, string path, int attributes) + : base(id) + { + this.Path = path; + this.Attributes = (WixFileSearchAttributes)attributes; + } + + public string Path { get; private set; } + public WixFileSearchAttributes Attributes { get; private set; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup for a file/directory search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement((0 == (this.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); + this.WriteWixSearchAttributes(writer); + writer.WriteAttributeString("Path", this.Path); + if (WixFileSearchAttributes.WantExists == (this.Attributes & WixFileSearchAttributes.WantExists)) + { + writer.WriteAttributeString("Type", "exists"); + } + else if (WixFileSearchAttributes.WantVersion == (this.Attributes & WixFileSearchAttributes.WantVersion)) + { + // Can never get here for DirectorySearch. + writer.WriteAttributeString("Type", "version"); + } + else + { + writer.WriteAttributeString("Type", "path"); + } + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs new file mode 100644 index 00000000..b3bf5fee --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs @@ -0,0 +1,67 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + + /// + /// Utility class for all WixProductSearches. + /// + internal class WixProductSearchInfo : WixSearchInfo + { + public WixProductSearchInfo(Row row) + : this((string)row[0], (string)row[1], (int)row[2]) + { + } + + public WixProductSearchInfo(string id, string guid, int attributes) + : base(id) + { + this.Guid = guid; + this.Attributes = (WixProductSearchAttributes)attributes; + } + + public string Guid { get; private set; } + public WixProductSearchAttributes Attributes { get; private set; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup for a product search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("MsiProductSearch"); + this.WriteWixSearchAttributes(writer); + + if (0 != (this.Attributes & WixProductSearchAttributes.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", this.Guid); + } + else + { + writer.WriteAttributeString("ProductCode", this.Guid); + } + + if (0 != (this.Attributes & WixProductSearchAttributes.Version)) + { + writer.WriteAttributeString("Type", "version"); + } + else if (0 != (this.Attributes & WixProductSearchAttributes.Language)) + { + writer.WriteAttributeString("Type", "language"); + } + else if (0 != (this.Attributes & WixProductSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (this.Attributes & WixProductSearchAttributes.Assignment)) + { + writer.WriteAttributeString("Type", "assignment"); + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs new file mode 100644 index 00000000..e25f25f4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs @@ -0,0 +1,92 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + + /// + /// Utility class for all WixRegistrySearches. + /// + internal class WixRegistrySearchInfo : WixSearchInfo + { + public WixRegistrySearchInfo(Row row) + : this((string)row[0], (int)row[1], (string)row[2], (string)row[3], (int)row[4]) + { + } + + public WixRegistrySearchInfo(string id, int root, string key, string value, int attributes) + : base(id) + { + this.Root = root; + this.Key = key; + this.Value = value; + this.Attributes = (WixRegistrySearchAttributes)attributes; + } + + public int Root { get; private set; } + public string Key { get; private set; } + public string Value { get; private set; } + public WixRegistrySearchAttributes Attributes { get; private set; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup for a registry search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("RegistrySearch"); + this.WriteWixSearchAttributes(writer); + + switch (this.Root) + { + case Core.Native.MsiInterop.MsidbRegistryRootClassesRoot: + writer.WriteAttributeString("Root", "HKCR"); + break; + case Core.Native.MsiInterop.MsidbRegistryRootCurrentUser: + writer.WriteAttributeString("Root", "HKCU"); + break; + case Core.Native.MsiInterop.MsidbRegistryRootLocalMachine: + writer.WriteAttributeString("Root", "HKLM"); + break; + case Core.Native.MsiInterop.MsidbRegistryRootUsers: + writer.WriteAttributeString("Root", "HKU"); + break; + } + + writer.WriteAttributeString("Key", this.Key); + + if (!String.IsNullOrEmpty(this.Value)) + { + writer.WriteAttributeString("Value", this.Value); + } + + bool existenceOnly = 0 != (this.Attributes & WixRegistrySearchAttributes.WantExists); + + writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); + + if (0 != (this.Attributes & WixRegistrySearchAttributes.Win64)) + { + writer.WriteAttributeString("Win64", "yes"); + } + + if (!existenceOnly) + { + if (0 != (this.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) + { + writer.WriteAttributeString("ExpandEnvironment", "yes"); + } + + // We *always* say this is VariableType="string". If we end up + // needing to be more specific, we will have to expand the "Format" + // attribute to allow "number" and "version". + + writer.WriteAttributeString("VariableType", "string"); + } + + writer.WriteEndElement(); + } + } + +} diff --git a/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs new file mode 100644 index 00000000..9ebca4ae --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs @@ -0,0 +1,53 @@ +// 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.Burn +{ + using System; + using System.Diagnostics; + using System.Xml; + using WixToolset.Data; + + /// + /// Utility base class for all WixSearches. + /// + internal abstract class WixSearchInfo + { + public WixSearchInfo(string id) + { + this.Id = id; + } + + public void AddWixSearchRowInfo(Row row) + { + Debug.Assert((string)row[0] == Id); + Variable = (string)row[1]; + Condition = (string)row[2]; + } + + public string Id { get; private set; } + public string Variable { get; private set; } + public string Condition { get; private set; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup a search. + /// + /// + public virtual void WriteXml(XmlTextWriter writer) + { + } + + /// + /// Writes attributes common to all WixSearch elements. + /// + /// + protected void WriteWixSearchAttributes(XmlTextWriter writer) + { + writer.WriteAttributeString("Id", this.Id); + writer.WriteAttributeString("Variable", this.Variable); + if (!String.IsNullOrEmpty(this.Condition)) + { + writer.WriteAttributeString("Condition", this.Condition); + } + } + } +} diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs new file mode 100644 index 00000000..ef4d362c --- /dev/null +++ b/src/WixToolset.Core.Burn/BundleBackend.cs @@ -0,0 +1,58 @@ +// 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.Burn +{ + using System; + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Inscribe; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + + internal class BundleBackend : IBackend + { + public BindResult Bind(IBindContext context) + { + BindBundleCommand command = new BindBundleCommand(context); + //command.DefaultCompressionLevel = context.DefaultCompressionLevel; + //command.Extensions = context.Extensions; + //command.IntermediateFolder = context.IntermediateFolder; + //command.Output = context.IntermediateRepresentation; + //command.OutputPath = context.OutputPath; + //command.PdbFile = context.OutputPdbPath; + //command.WixVariableResolver = context.WixVariableResolver; + command.Execute(); + + return new BindResult(command.FileTransfers, command.ContentFilePaths); + } + + public bool Inscribe(IInscribeContext context) + { + if (String.IsNullOrEmpty(context.SignedEngineFile)) + { + var command = new InscribeBundleCommand(context); + return command.Execute(); + } + else + { + var command = new InscribeBundleEngineCommand(context); + return command.Execute(); + } + } + + public Output Unbind(IUnbindContext context) + { + string uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); + string acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); + + using (BurnReader reader = BurnReader.Open(context.InputFilePath)) + { + reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); + reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); + } + + return null; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs new file mode 100644 index 00000000..bac8633b --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs @@ -0,0 +1,112 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class AutomaticallySlipstreamPatchesCommand + { + public IEnumerable PackageFacades { private get; set; } + + public Table WixBundlePatchTargetCodeTable { private get; set; } + + public Table SlipstreamMspTable { private get; set; } + + public void Execute() + { + List msiPackages = new List(); + Dictionary> targetsProductCode = new Dictionary>(); + Dictionary> targetsUpgradeCode = new Dictionary>(); + + foreach (PackageFacade facade in this.PackageFacades) + { + if (WixBundlePackageType.Msi == facade.Package.Type) + { + // Keep track of all MSI packages. + msiPackages.Add(facade.MsiPackage); + } + else if (WixBundlePackageType.Msp == facade.Package.Type && facade.MspPackage.Slipstream) + { + IEnumerable patchTargetCodeRows = this.WixBundlePatchTargetCodeTable.RowsAs().Where(r => r.MspPackageId == facade.Package.WixChainItemId); + + // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. + foreach (WixBundlePatchTargetCodeRow row in patchTargetCodeRows) + { + if (row.TargetsProductCode) + { + List rows; + if (!targetsProductCode.TryGetValue(row.TargetCode, out rows)) + { + rows = new List(); + targetsProductCode.Add(row.TargetCode, rows); + } + + rows.Add(row); + } + else if (row.TargetsUpgradeCode) + { + List rows; + if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows)) + { + rows = new List(); + targetsUpgradeCode.Add(row.TargetCode, rows); + } + } + } + } + } + + RowIndexedList slipstreamMspRows = new RowIndexedList(SlipstreamMspTable); + + // Loop through the MSI and slipstream patches targeting it. + foreach (WixBundleMsiPackageRow msi in msiPackages) + { + List rows; + if (targetsProductCode.TryGetValue(msi.ProductCode, out rows)) + { + foreach (WixBundlePatchTargetCodeRow row in rows) + { + Debug.Assert(row.TargetsProductCode); + Debug.Assert(!row.TargetsUpgradeCode); + + Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); + slipstreamMspRow[0] = msi.ChainPackageId; + slipstreamMspRow[1] = row.MspPackageId; + + if (slipstreamMspRows.TryAdd(slipstreamMspRow)) + { + SlipstreamMspTable.Rows.Add(slipstreamMspRow); + } + } + + rows = null; + } + + if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows)) + { + foreach (WixBundlePatchTargetCodeRow row in rows) + { + Debug.Assert(!row.TargetsProductCode); + Debug.Assert(row.TargetsUpgradeCode); + + Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); + slipstreamMspRow[0] = msi.ChainPackageId; + slipstreamMspRow[1] = row.MspPackageId; + + if (slipstreamMspRows.TryAdd(slipstreamMspRow)) + { + SlipstreamMspTable.Rows.Add(slipstreamMspRow); + } + } + + rows = null; + } + } + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs new file mode 100644 index 00000000..0baa6094 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -0,0 +1,378 @@ +// 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.Burn.Bundles +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Data; + + /// + /// Common functionality for Burn PE Writer & Reader for the WiX toolset. + /// + /// This class encapsulates common functionality related to + /// bundled/chained setup packages. + /// + /// + internal abstract class BurnCommon : IDisposable + { + public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; + public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; + public const string BurnUXContainerPayloadIdFormat = "p{0}"; + public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; + + // See WinNT.h for details about the PE format, including the + // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, + // IMAGE_FILE_HEADER, etc. + protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64; + protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0; + protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60; + + protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20) + protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0; + protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6; + protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20; + + protected const UInt32 IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER. + protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); + + protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20; + + protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs. + protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4; + protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; + + protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D; + protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550; + protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword. + + // The ".wixburn" section contains: + // 0- 3: magic number + // 4- 7: version + // 8-23: bundle GUID + // 24-27: engine (stub) size + // 28-31: original checksum + // 32-35: original signature offset + // 36-39: original signature size + // 40-43: container type (1 = CAB) + // 44-47: container count + // 48-51: byte count of manifest + UX container + // 52-55: byte count of attached container + protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0; + protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4; + protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8; + protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24; + protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28; + protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32; + protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36; + protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40; + protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44; + protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48; + protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52; + protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD) + + protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300; + protected const UInt32 BURN_SECTION_VERSION = 0x00000002; + + protected string fileExe; + protected UInt32 peOffset = UInt32.MaxValue; + protected UInt16 sections = UInt16.MaxValue; + protected UInt32 firstSectionOffset = UInt32.MaxValue; + protected UInt32 checksumOffset; + protected UInt32 certificateTableSignatureOffset; + protected UInt32 certificateTableSignatureSize; + protected UInt32 wixburnDataOffset = UInt32.MaxValue; + + // TODO: does this enum exist in another form somewhere? + /// + /// The types of attached containers that BurnWriter supports. + /// + public enum Container + { + Nothing = 0, + UX, + Attached + } + + /// + /// Creates a BurnCommon for re-writing a PE file. + /// + /// File to modify in-place. + /// GUID for the bundle. + public BurnCommon(string fileExe) + { + this.fileExe = fileExe; + } + + public UInt32 Checksum { get; protected set; } + public UInt32 SignatureOffset { get; protected set; } + public UInt32 SignatureSize { get; protected set; } + public UInt32 Version { get; protected set; } + public UInt32 StubSize { get; protected set; } + public UInt32 OriginalChecksum { get; protected set; } + public UInt32 OriginalSignatureOffset { get; protected set; } + public UInt32 OriginalSignatureSize { get; protected set; } + public UInt32 EngineSize { get; protected set; } + public UInt32 ContainerCount { get; protected set; } + public UInt32 UXAddress { get; protected set; } + public UInt32 UXSize { get; protected set; } + public UInt32 AttachedContainerAddress { get; protected set; } + public UInt32 AttachedContainerSize { get; protected set; } + + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + /// + /// Copies one stream to another. + /// + /// Input stream. + /// Output stream. + /// Optional count of bytes to copy. 0 indicates whole input stream from current should be copied. + protected static int CopyStream(Stream input, Stream output, int size) + { + byte[] bytes = new byte[4096]; + int total = 0; + int read = 0; + do + { + read = Math.Min(bytes.Length, size - total); + read = input.Read(bytes, 0, read); + if (0 == read) + { + break; + } + + output.Write(bytes, 0, read); + total += read; + } while (0 == size || total < size); + + return total; + } + + /// + /// Initialize the common information about a Burn engine. + /// + /// Binary reader open against a Burn engine. + /// True if initialized. + protected bool Initialize(BinaryReader reader) + { + if (!GetWixburnSectionInfo(reader)) + { + return false; + } + + reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin); + byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE); + UInt32 uint32 = 0; + + uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC); + if (BURN_SECTION_MAGIC != uint32) + { + Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); + return false; + } + + this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); + if (BURN_SECTION_VERSION != this.Version) + { + Messaging.Instance.OnMessage(WixErrors.BundleTooNew(this.fileExe, this.Version)); + return false; + } + + uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now + if (1 != uint32) + { + Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); + return false; + } + + this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE); + this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM); + this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET); + this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE); + + this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT); + this.UXAddress = this.StubSize; + this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE); + + // If there is an original signature use that to determine the engine size. + if (0 < this.OriginalSignatureOffset) + { + this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize; + } + else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature. + { + this.EngineSize = this.SignatureOffset + this.SignatureSize; + } + else // just use the stub and UX container as the size of the engine. + { + this.EngineSize = this.StubSize + this.UXSize; + } + + this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0; + this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0; + + return true; + } + + protected virtual void Dispose(bool disposing) + { + } + + /// + /// Finds the ".wixburn" section in the current exe. + /// + /// true if the ".wixburn" section is successfully found; false otherwise + private bool GetWixburnSectionInfo(BinaryReader reader) + { + if (UInt32.MaxValue == this.wixburnDataOffset) + { + if (!EnsureNTHeader(reader)) + { + return false; + } + + UInt32 wixburnSectionOffset = UInt32.MaxValue; + byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE]; + + reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin); + for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex) + { + reader.Read(bytes, 0, bytes.Length); + + if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME)) + { + wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex); + break; + } + } + + if (UInt32.MaxValue == wixburnSectionOffset) + { + Messaging.Instance.OnMessage(WixErrors.StubMissingWixburnSection(this.fileExe)); + return false; + } + + // we need 56 bytes for the manifest header, which is always going to fit in + // the smallest alignment (512 bytes), but just to be paranoid... + if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA)) + { + Messaging.Instance.OnMessage(WixErrors.StubWixburnSectionTooSmall(this.fileExe)); + return false; + } + + this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA); + } + + return true; + } + + /// + /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe. + /// + /// true if the exe is a Windows executable; false otherwise + private bool EnsureNTHeader(BinaryReader reader) + { + if (UInt32.MaxValue == this.firstSectionOffset) + { + if (!EnsureDosHeader(reader)) + { + return false; + } + + reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin); + byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE); + + // Verify the NT signature... + if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE)) + { + Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); + return false; + } + + ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER); + + this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS); + this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader; + + this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM; + this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE; + this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset. + + bytes = reader.ReadBytes(sizeOptionalHeader); + this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM); + this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE); + this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4); + } + + return true; + } + + /// + /// Checks for a valid DOS header in the current exe. + /// + /// true if the exe starts with a DOS stub; false otherwise + private bool EnsureDosHeader(BinaryReader reader) + { + if (UInt32.MaxValue == this.peOffset) + { + byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE); + + // Verify the DOS 'MZ' signature. + if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC)) + { + Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); + return false; + } + + this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER); + } + + return true; + } + + /// + /// Reads a UInt16 value in little-endian format from an offset in an array of bytes. + /// + /// Array from which to read. + /// Beginning offset from which to read. + /// value at offset + private static UInt16 ReadUInt16(byte[] bytes, UInt32 offset) + { + Debug.Assert(offset + 2 <= bytes.Length); + return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8)); + } + + /// + /// Reads a UInt32 value in little-endian format from an offset in an array of bytes. + /// + /// Array from which to read. + /// Beginning offset from which to read. + /// value at offset + private static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) + { + Debug.Assert(offset + 4 <= bytes.Length); + return (UInt32)(bytes[offset] + (bytes[offset + 1] << 8) + (bytes[offset + 2] << 16) + (bytes[offset + 3] << 24)); + } + + /// + /// Reads a UInt64 value in little-endian format from an offset in an array of bytes. + /// + /// Array from which to read. + /// Beginning offset from which to read. + /// value at offset + private static UInt64 ReadUInt64(byte[] bytes, UInt32 offset) + { + Debug.Assert(offset + 8 <= bytes.Length); + return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)(BurnCommon.ReadUInt32(bytes, offset + 4)) << 32); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/WixToolset.Core.Burn/Bundles/BurnReader.cs new file mode 100644 index 00000000..261ef7b4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/BurnReader.cs @@ -0,0 +1,220 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Xml; + using WixToolset.Core.Cab; + + /// + /// Burn PE reader for the WiX toolset. + /// + /// This class encapsulates reading from a stub EXE with containers attached + /// for dissecting bundled/chained setup packages. + /// + /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) + /// { + /// reader.ExtractUXContainer(file1, tempFolder); + /// } + /// + internal class BurnReader : BurnCommon + { + private bool disposed; + + private bool invalidBundle; + private BinaryReader binaryReader; + private List attachedContainerPayloadNames; + + /// + /// Creates a BurnReader for reading a PE file. + /// + /// File to read. + private BurnReader(string fileExe) + : base(fileExe) + { + this.attachedContainerPayloadNames = new List(); + } + + /// + /// Gets the underlying stream. + /// + public Stream Stream + { + get + { + return (null != this.binaryReader) ? this.binaryReader.BaseStream : null; + } + } + + internal static BurnReader Open(object inputFilePath) + { + throw new NotImplementedException(); + } + + /// + /// Opens a Burn reader. + /// + /// Path to file. + /// Burn reader. + public static BurnReader Open(string fileExe) + { + BurnReader reader = new BurnReader(fileExe); + + reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); + if (!reader.Initialize(reader.binaryReader)) + { + reader.invalidBundle = true; + } + + return reader; + } + + /// + /// Gets the UX container from the exe and extracts its contents to the output directory. + /// + /// Directory to write extracted files to. + /// True if successful, false otherwise + public bool ExtractUXContainer(string outputDirectory, string tempDirectory) + { + // No UX container to extract + if (this.UXAddress == 0 || this.UXSize == 0) + { + return false; + } + + if (this.invalidBundle) + { + return false; + } + + Directory.CreateDirectory(outputDirectory); + string tempCabPath = Path.Combine(tempDirectory, "ux.cab"); + string manifestOriginalPath = Path.Combine(outputDirectory, "0"); + string manifestPath = Path.Combine(outputDirectory, "manifest.xml"); + + this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); + using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) + { + BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize); + } + + using (var extract = new WixExtractCab()) + { + extract.Extract(tempCabPath, outputDirectory); + } + + Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); + File.Delete(manifestPath); + File.Move(manifestOriginalPath, manifestPath); + + XmlDocument document = new XmlDocument(); + document.Load(manifestPath); + XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace); + XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); + XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); + + foreach (XmlNode uxPayload in uxPayloads) + { + XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); + XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); + + string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); + string destinationPath = Path.Combine(outputDirectory, filePathNode.Value); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + File.Delete(destinationPath); + File.Move(sourcePath, destinationPath); + } + + foreach (XmlNode payload in payloads) + { + XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); + XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath"); + XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging"); + + string sourcePath = sourcePathNode.Value; + string destinationPath = filePathNode.Value; + string packaging = packagingNode.Value; + + if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) + { + this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); + } + } + + return true; + } + + internal void ExtractUXContainer(string uxExtractPath, object intermediateFolder) + { + throw new NotImplementedException(); + } + + /// + /// Gets the attached container from the exe and extracts its contents to the output directory. + /// + /// Directory to write extracted files to. + /// True if successful, false otherwise + public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory) + { + // No attached container to extract + if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0) + { + return false; + } + + if (this.invalidBundle) + { + return false; + } + + Directory.CreateDirectory(outputDirectory); + string tempCabPath = Path.Combine(tempDirectory, "attached.cab"); + + this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin); + using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) + { + BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize); + } + + using (WixExtractCab extract = new WixExtractCab()) + { + extract.Extract(tempCabPath, outputDirectory); + } + + foreach (DictionaryEntry entry in this.attachedContainerPayloadNames) + { + string sourcePath = Path.Combine(outputDirectory, (string)entry.Key); + string destinationPath = Path.Combine(outputDirectory, (string)entry.Value); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + File.Delete(destinationPath); + File.Move(sourcePath, destinationPath); + } + + return true; + } + + /// + /// Dispose object. + /// + /// True when releasing managed objects. + protected override void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing && this.binaryReader != null) + { + this.binaryReader.Close(); + this.binaryReader = null; + } + + this.disposed = true; + } + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs new file mode 100644 index 00000000..e7365212 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs @@ -0,0 +1,239 @@ +// 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.Burn.Bundles +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Data; + + /// + /// Burn PE writer for the WiX toolset. + /// + /// This class encapsulates reading/writing to a stub EXE for + /// creating bundled/chained setup packages. + /// + /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) + /// { + /// writer.AppendContainer(file1, BurnWriter.Container.UX); + /// writer.AppendContainer(file2, BurnWriter.Container.Attached); + /// } + /// + internal class BurnWriter : BurnCommon + { + private bool disposed; + private bool invalidBundle; + private BinaryWriter binaryWriter; + + /// + /// Creates a BurnWriter for re-writing a PE file. + /// + /// File to modify in-place. + /// GUID for the bundle. + private BurnWriter(string fileExe) + : base(fileExe) + { + } + + /// + /// Opens a Burn writer. + /// + /// Path to file. + /// Burn writer. + public static BurnWriter Open(string fileExe) + { + BurnWriter writer = new BurnWriter(fileExe); + + using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) + { + if (!writer.Initialize(binaryReader)) + { + writer.invalidBundle = true; + } + } + + if (!writer.invalidBundle) + { + writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); + } + + return writer; + } + + /// + /// Update the ".wixburn" section data. + /// + /// Size of the stub engine "burn.exe". + /// Unique identifier for this bundle. + /// + public bool InitializeBundleSectionData(long stubSize, Guid bundleId) + { + if (this.invalidBundle) + { + return false; + } + + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); + + Messaging.Instance.OnMessage(WixVerboses.BundleGuid(bundleId.ToString("B"))); + this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); + this.binaryWriter.Write(bundleId.ToByteArray()); + + this.StubSize = (uint)stubSize; + + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0); + this.binaryWriter.BaseStream.Flush(); + + this.EngineSize = this.StubSize; + + return true; + } + + /// + /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. + /// + /// File path to append to the current exe. + /// Container section represented by the fileContainer. + /// true if the container data is successfully appended; false otherwise + public bool AppendContainer(string fileContainer, BurnCommon.Container container) + { + using (FileStream reader = File.OpenRead(fileContainer)) + { + return this.AppendContainer(reader, reader.Length, container); + } + } + + /// + /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. + /// + /// File stream to append to the current exe. + /// Size of container to append. + /// Container section represented by the fileContainer. + /// true if the container data is successfully appended; false otherwise + public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) + { + UInt32 burnSectionCount = 0; + UInt32 burnSectionOffsetSize = 0; + + switch (container) + { + case Container.UX: + burnSectionCount = 1; + burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE; + // TODO: verify that the size in the section data is 0 or the same size. + this.EngineSize += (uint)containerSize; + this.UXSize = (uint)containerSize; + break; + + case Container.Attached: + burnSectionCount = 2; + burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE; + // TODO: verify that the size in the section data is 0 or the same size. + this.AttachedContainerSize = (uint)containerSize; + break; + + default: + Debug.Assert(false); + return false; + } + + return AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); + } + + public void RememberThenResetSignature() + { + if (this.invalidBundle) + { + return; + } + + this.OriginalChecksum = this.Checksum; + this.OriginalSignatureOffset = this.SignatureOffset; + this.OriginalSignatureSize = this.SignatureSize; + + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); + + this.Checksum = 0; + this.SignatureOffset = 0; + this.SignatureSize = 0; + + this.WriteToOffset(this.checksumOffset, this.Checksum); + this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); + this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); + } + + /// + /// Dispose object. + /// + /// True when releasing managed objects. + protected override void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing && this.binaryWriter != null) + { + this.binaryWriter.Close(); + this.binaryWriter = null; + } + + this.disposed = true; + } + } + + /// + /// Appends a container to the exe and updates the ".wixburn" section data to point to it. + /// + /// File stream to append to the current exe. + /// Offset of size field for this container in ".wixburn" section data. + /// true if the container data is successfully appended; false otherwise + private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount) + { + if (this.invalidBundle) + { + return false; + } + + // Update the ".wixburn" section data + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); + this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); + + // Append the container to the end of the existing bits. + this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); + BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); + this.binaryWriter.BaseStream.Flush(); + + return true; + } + + /// + /// Writes the value to an offset in the Burn section data. + /// + /// Offset in to the Burn section data. + /// Value to write. + private void WriteToBurnSectionOffset(uint offset, uint value) + { + this.WriteToOffset(this.wixburnDataOffset + offset, value); + } + + /// + /// Writes the value to an offset in the Burn stub. + /// + /// Offset in to the Burn stub. + /// Value to write. + private void WriteToOffset(uint offset, uint value) + { + this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); + this.binaryWriter.Write(value); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs new file mode 100644 index 00000000..58814efc --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -0,0 +1,241 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class CreateBootstrapperApplicationManifestCommand + { + public WixBundleRow BundleRow { private get; set; } + + public IEnumerable ChainPackages { private get; set; } + + public int LastUXPayloadIndex { private get; set; } + + public IEnumerable MsiFeatures { private get; set; } + + public Output Output { private get; set; } + + public RowDictionary Payloads { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public string TempFilesLocation { private get; set; } + + public WixBundlePayloadRow BootstrapperApplicationManifestPayloadRow { get; private set; } + + public void Execute() + { + this.GenerateBAManifestBundleTables(); + + this.GenerateBAManifestMsiFeatureTables(); + + this.GenerateBAManifestPackageTables(); + + this.GenerateBAManifestPayloadTables(); + + string baManifestPath = Path.Combine(this.TempFilesLocation, "wix-badata.xml"); + + this.CreateBootstrapperApplicationManifest(baManifestPath); + + this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath); + } + + private void GenerateBAManifestBundleTables() + { + Table wixBundlePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleProperties"]); + + Row row = wixBundlePropertiesTable.CreateRow(this.BundleRow.SourceLineNumbers); + row[0] = this.BundleRow.Name; + row[1] = this.BundleRow.LogPathVariable; + row[2] = (YesNoDefaultType.Yes == this.BundleRow.Compressed) ? "yes" : "no"; + row[3] = this.BundleRow.BundleId.ToString("B"); + row[4] = this.BundleRow.UpgradeCode; + row[5] = this.BundleRow.PerMachine ? "yes" : "no"; + } + + private void GenerateBAManifestPackageTables() + { + Table wixPackagePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageProperties"]); + + foreach (PackageFacade package in this.ChainPackages) + { + WixBundlePayloadRow packagePayload = this.Payloads[package.Package.PackagePayload]; + + Row row = wixPackagePropertiesTable.CreateRow(package.Package.SourceLineNumbers); + row[0] = package.Package.WixChainItemId; + row[1] = (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"; + row[2] = package.Package.DisplayName; + row[3] = package.Package.Description; + row[4] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?) + row[5] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed) + row[6] = package.Package.InstallSize.Value.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space) + row[7] = package.Package.Type.ToString(); + row[8] = package.Package.Permanent ? "yes" : "no"; + row[9] = package.Package.LogPathVariable; + row[10] = package.Package.RollbackLogPathVariable; + row[11] = (PackagingType.Embedded == packagePayload.Packaging) ? "yes" : "no"; + + if (WixBundlePackageType.Msi == package.Package.Type) + { + row[12] = package.MsiPackage.DisplayInternalUI ? "yes" : "no"; + + if (!String.IsNullOrEmpty(package.MsiPackage.ProductCode)) + { + row[13] = package.MsiPackage.ProductCode; + } + + if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) + { + row[14] = package.MsiPackage.UpgradeCode; + } + } + else if (WixBundlePackageType.Msp == package.Package.Type) + { + row[12] = package.MspPackage.DisplayInternalUI ? "yes" : "no"; + + if (!String.IsNullOrEmpty(package.MspPackage.PatchCode)) + { + row[13] = package.MspPackage.PatchCode; + } + } + + if (!String.IsNullOrEmpty(package.Package.Version)) + { + row[15] = package.Package.Version; + } + + if (!String.IsNullOrEmpty(package.Package.InstallCondition)) + { + row[16] = package.Package.InstallCondition; + } + + switch (package.Package.Cache) + { + case YesNoAlwaysType.No: + row[17] = "no"; + break; + case YesNoAlwaysType.Yes: + row[17] = "yes"; + break; + case YesNoAlwaysType.Always: + row[17] = "always"; + break; + } + } + } + + private void GenerateBAManifestMsiFeatureTables() + { + Table wixPackageFeatureInfoTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageFeatureInfo"]); + + foreach (WixBundleMsiFeatureRow feature in this.MsiFeatures) + { + Row row = wixPackageFeatureInfoTable.CreateRow(feature.SourceLineNumbers); + row[0] = feature.ChainPackageId; + row[1] = feature.Name; + row[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture); + row[3] = feature.Parent; + row[4] = feature.Title; + row[5] = feature.Description; + row[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture); + row[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture); + row[8] = feature.Directory; + row[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture); + } + + } + + private void GenerateBAManifestPayloadTables() + { + Table wixPayloadPropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPayloadProperties"]); + + foreach (WixBundlePayloadRow payload in this.Payloads.Values) + { + WixPayloadPropertiesRow row = (WixPayloadPropertiesRow)wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers); + row.Id = payload.Id; + row.Package = payload.Package; + row.Container = payload.Container; + row.Name = payload.Name; + row.Size = payload.FileSize.ToString(); + row.DownloadUrl = payload.DownloadUrl; + row.LayoutOnly = payload.LayoutOnly ? "yes" : "no"; + } + } + + private void CreateBootstrapperApplicationManifest(string path) + { + using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/2010/BootstrapperApplicationData"); + + foreach (Table table in this.Output.Tables) + { + if (table.Definition.BootstrapperApplicationData) + { + // We simply assert that the table (and field) name is valid, because + // this is up to the extension developer to get right. An author will + // only affect the attribute value, and that will get properly escaped. +#if DEBUG + Debug.Assert(Common.IsIdentifier(table.Name)); + foreach (ColumnDefinition column in table.Definition.Columns) + { + Debug.Assert(Common.IsIdentifier(column.Name)); + } +#endif // DEBUG + + foreach (Row row in table.Rows) + { + writer.WriteStartElement(table.Name); + + foreach (Field field in row.Fields) + { + if (null != field.Data) + { + writer.WriteAttributeString(field.Column.Name, field.Data.ToString()); + } + } + + writer.WriteEndElement(); + } + } + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + } + + private WixBundlePayloadRow CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) + { + Table payloadTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePayload"]); + WixBundlePayloadRow row = (WixBundlePayloadRow)payloadTable.CreateRow(this.BundleRow.SourceLineNumbers); + row.Id = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); + row.Name = "BootstrapperApplicationData.xml"; + row.SourceFile = baManifestPath; + row.Compressed = YesNoDefaultType.Yes; + row.UnresolvedSourceFile = baManifestPath; + row.Container = Compiler.BurnUXContainerId; + row.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex); + row.Packaging = PackagingType.Embedded; + + FileInfo fileInfo = new FileInfo(row.SourceFile); + + row.FileSize = (int)fileInfo.Length; + + row.Hash = Common.GetFileHash(fileInfo.FullName); + + return row; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs new file mode 100644 index 00000000..772265a0 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -0,0 +1,686 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + + internal class CreateBurnManifestCommand + { + public IEnumerable BackendExtensions { private get; set; } + + public Output Output { private get; set; } + + public string ExecutableName { private get; set; } + + public WixBundleRow BundleInfo { private get; set; } + + public WixChainRow Chain { private get; set; } + + public string OutputPath { private get; set; } + + public IEnumerable RollbackBoundaries { private get; set; } + + public IEnumerable OrderedPackages { private get; set; } + + public IEnumerable OrderedSearches { private get; set; } + + public Dictionary Payloads { private get; set; } + + public Dictionary Containers { private get; set; } + + public IEnumerable UXContainerPayloads { private get; set; } + + public IEnumerable Catalogs { private get; set; } + + public void Execute() + { + using (XmlTextWriter writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) + { + writer.WriteStartDocument(); + + writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); + + // Write the condition, if there is one + if (null != this.BundleInfo.Condition) + { + writer.WriteElementString("Condition", this.BundleInfo.Condition); + } + + // Write the log element if default logging wasn't disabled. + if (!String.IsNullOrEmpty(this.BundleInfo.LogPrefix)) + { + writer.WriteStartElement("Log"); + if (!String.IsNullOrEmpty(this.BundleInfo.LogPathVariable)) + { + writer.WriteAttributeString("PathVariable", this.BundleInfo.LogPathVariable); + } + writer.WriteAttributeString("Prefix", this.BundleInfo.LogPrefix); + writer.WriteAttributeString("Extension", this.BundleInfo.LogExtension); + writer.WriteEndElement(); + } + + + // Get update if specified. + WixBundleUpdateRow updateRow = this.Output.Tables["WixBundleUpdate"].RowsAs().FirstOrDefault(); + + if (null != updateRow) + { + writer.WriteStartElement("Update"); + writer.WriteAttributeString("Location", updateRow.Location); + writer.WriteEndElement(); // + } + + // Write the RelatedBundle elements + + // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates + // enumeration in the index row list is not used). + RowIndexedList relatedBundles = new RowIndexedList(this.Output.Tables["WixRelatedBundle"]); + + foreach (WixRelatedBundleRow relatedBundle in relatedBundles) + { + writer.WriteStartElement("RelatedBundle"); + writer.WriteAttributeString("Id", relatedBundle.Id); + writer.WriteAttributeString("Action", Convert.ToString(relatedBundle.Action, CultureInfo.InvariantCulture)); + writer.WriteEndElement(); + } + + // Write the variables + IEnumerable variables = this.Output.Tables["WixBundleVariable"].RowsAs(); + + foreach (WixBundleVariableRow variable in variables) + { + writer.WriteStartElement("Variable"); + writer.WriteAttributeString("Id", variable.Id); + if (null != variable.Type) + { + writer.WriteAttributeString("Value", variable.Value); + writer.WriteAttributeString("Type", variable.Type); + } + writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); + writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); + writer.WriteEndElement(); + } + + // Write the searches + foreach (WixSearchInfo searchinfo in this.OrderedSearches) + { + searchinfo.WriteXml(writer); + } + + // write the UX element + writer.WriteStartElement("UX"); + if (!String.IsNullOrEmpty(this.BundleInfo.SplashScreenBitmapPath)) + { + writer.WriteAttributeString("SplashScreen", "yes"); + } + + // write the UX allPayloads... + foreach (WixBundlePayloadRow payload in this.UXContainerPayloads) + { + writer.WriteStartElement("Payload"); + this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); // + + // write the catalog elements + if (this.Catalogs.Any()) + { + foreach (WixBundleCatalogRow catalog in this.Catalogs) + { + writer.WriteStartElement("Catalog"); + writer.WriteAttributeString("Id", catalog.Id); + writer.WriteAttributeString("Payload", catalog.Payload); + writer.WriteEndElement(); + } + } + + foreach (WixBundleContainerRow container in this.Containers.Values) + { + if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) + { + writer.WriteStartElement("Container"); + this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); + writer.WriteEndElement(); + } + } + + foreach (WixBundlePayloadRow payload in this.Payloads.Values) + { + if (PackagingType.Embedded == payload.Packaging && Compiler.BurnUXContainerId != payload.Container) + { + writer.WriteStartElement("Payload"); + this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); + writer.WriteEndElement(); + } + else if (PackagingType.External == payload.Packaging) + { + writer.WriteStartElement("Payload"); + this.WriteBurnManifestPayloadAttributes(writer, payload, false, this.Payloads); + writer.WriteEndElement(); + } + } + + foreach (WixBundleRollbackBoundaryRow rollbackBoundary in this.RollbackBoundaries) + { + writer.WriteStartElement("RollbackBoundary"); + writer.WriteAttributeString("Id", rollbackBoundary.ChainPackageId); + writer.WriteAttributeString("Vital", YesNoType.Yes == rollbackBoundary.Vital ? "yes" : "no"); + writer.WriteAttributeString("Transaction", YesNoType.Yes == rollbackBoundary.Transaction ? "yes" : "no"); + writer.WriteEndElement(); + } + + // Write the registration information... + writer.WriteStartElement("Registration"); + + writer.WriteAttributeString("Id", this.BundleInfo.BundleId.ToString("B")); + writer.WriteAttributeString("ExecutableName", this.ExecutableName); + writer.WriteAttributeString("PerMachine", this.BundleInfo.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Tag", this.BundleInfo.Tag); + writer.WriteAttributeString("Version", this.BundleInfo.Version); + writer.WriteAttributeString("ProviderKey", this.BundleInfo.ProviderKey); + + writer.WriteStartElement("Arp"); + writer.WriteAttributeString("Register", (0 < this.BundleInfo.DisableModify && this.BundleInfo.DisableRemove) ? "no" : "yes"); // do not register if disabled modify and remove. + writer.WriteAttributeString("DisplayName", this.BundleInfo.Name); + writer.WriteAttributeString("DisplayVersion", this.BundleInfo.Version); + + if (!String.IsNullOrEmpty(this.BundleInfo.Publisher)) + { + writer.WriteAttributeString("Publisher", this.BundleInfo.Publisher); + } + + if (!String.IsNullOrEmpty(this.BundleInfo.HelpLink)) + { + writer.WriteAttributeString("HelpLink", this.BundleInfo.HelpLink); + } + + if (!String.IsNullOrEmpty(this.BundleInfo.HelpTelephone)) + { + writer.WriteAttributeString("HelpTelephone", this.BundleInfo.HelpTelephone); + } + + if (!String.IsNullOrEmpty(this.BundleInfo.AboutUrl)) + { + writer.WriteAttributeString("AboutUrl", this.BundleInfo.AboutUrl); + } + + if (!String.IsNullOrEmpty(this.BundleInfo.UpdateUrl)) + { + writer.WriteAttributeString("UpdateUrl", this.BundleInfo.UpdateUrl); + } + + if (!String.IsNullOrEmpty(this.BundleInfo.ParentName)) + { + writer.WriteAttributeString("ParentDisplayName", this.BundleInfo.ParentName); + } + + if (1 == this.BundleInfo.DisableModify) + { + writer.WriteAttributeString("DisableModify", "yes"); + } + else if (2 == this.BundleInfo.DisableModify) + { + writer.WriteAttributeString("DisableModify", "button"); + } + + if (this.BundleInfo.DisableRemove) + { + writer.WriteAttributeString("DisableRemove", "yes"); + } + writer.WriteEndElement(); // + + // Get update registration if specified. + WixUpdateRegistrationRow updateRegistrationInfo = this.Output.Tables["WixUpdateRegistration"].RowsAs().FirstOrDefault(); + + if (null != updateRegistrationInfo) + { + writer.WriteStartElement("Update"); // + writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer); + + if (!String.IsNullOrEmpty(updateRegistrationInfo.Department)) + { + writer.WriteAttributeString("Department", updateRegistrationInfo.Department); + } + + if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily)) + { + writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily); + } + + writer.WriteAttributeString("Name", updateRegistrationInfo.Name); + writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification); + writer.WriteEndElement(); // + } + + IEnumerable bundleTags = this.Output.Tables["WixBundleTag"].RowsAs(); + + foreach (Row row in bundleTags) + { + writer.WriteStartElement("SoftwareTag"); + writer.WriteAttributeString("Filename", (string)row[0]); + writer.WriteAttributeString("Regid", (string)row[1]); + writer.WriteCData((string)row[4]); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); // + + // write the Chain... + writer.WriteStartElement("Chain"); + if (this.Chain.DisableRollback) + { + writer.WriteAttributeString("DisableRollback", "yes"); + } + + if (this.Chain.DisableSystemRestore) + { + writer.WriteAttributeString("DisableSystemRestore", "yes"); + } + + if (this.Chain.ParallelCache) + { + writer.WriteAttributeString("ParallelCache", "yes"); + } + + // Index a few tables by package. + ILookup targetCodesByPatch = this.Output.Tables["WixBundlePatchTargetCode"].RowsAs().ToLookup(r => r.MspPackageId); + ILookup msiFeaturesByPackage = this.Output.Tables["WixBundleMsiFeature"].RowsAs().ToLookup(r => r.ChainPackageId); + ILookup msiPropertiesByPackage = this.Output.Tables["WixBundleMsiProperty"].RowsAs().ToLookup(r => r.ChainPackageId); + ILookup payloadsByPackage = this.Payloads.Values.ToLookup(p => p.Package); + ILookup relatedPackagesByPackage = this.Output.Tables["WixBundleRelatedPackage"].RowsAs().ToLookup(r => r.ChainPackageId); + ILookup slipstreamMspsByPackage = this.Output.Tables["WixBundleSlipstreamMsp"].RowsAs().ToLookup(r => r.ChainPackageId); + ILookup exitCodesByPackage = this.Output.Tables["WixBundlePackageExitCode"].RowsAs().ToLookup(r => r.ChainPackageId); + ILookup commandLinesByPackage = this.Output.Tables["WixBundlePackageCommandLine"].RowsAs().ToLookup(r => r.ChainPackageId); + + // Build up the list of target codes from all the MSPs in the chain. + List targetCodes = new List(); + + foreach (PackageFacade package in this.OrderedPackages) + { + writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.Package.Type)); + + writer.WriteAttributeString("Id", package.Package.WixChainItemId); + + switch (package.Package.Cache) + { + case YesNoAlwaysType.No: + writer.WriteAttributeString("Cache", "no"); + break; + case YesNoAlwaysType.Yes: + writer.WriteAttributeString("Cache", "yes"); + break; + case YesNoAlwaysType.Always: + writer.WriteAttributeString("Cache", "always"); + break; + } + + writer.WriteAttributeString("CacheId", package.Package.CacheId); + writer.WriteAttributeString("InstallSize", Convert.ToString(package.Package.InstallSize)); + writer.WriteAttributeString("Size", Convert.ToString(package.Package.Size)); + writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.Package.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Permanent", package.Package.Permanent ? "yes" : "no"); + writer.WriteAttributeString("Vital", (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"); + + if (null != package.Package.RollbackBoundary) + { + writer.WriteAttributeString("RollbackBoundaryForward", package.Package.RollbackBoundary); + } + + if (!String.IsNullOrEmpty(package.Package.RollbackBoundaryBackward)) + { + writer.WriteAttributeString("RollbackBoundaryBackward", package.Package.RollbackBoundaryBackward); + } + + if (!String.IsNullOrEmpty(package.Package.LogPathVariable)) + { + writer.WriteAttributeString("LogPathVariable", package.Package.LogPathVariable); + } + + if (!String.IsNullOrEmpty(package.Package.RollbackLogPathVariable)) + { + writer.WriteAttributeString("RollbackLogPathVariable", package.Package.RollbackLogPathVariable); + } + + if (!String.IsNullOrEmpty(package.Package.InstallCondition)) + { + writer.WriteAttributeString("InstallCondition", package.Package.InstallCondition); + } + + if (WixBundlePackageType.Exe == package.Package.Type) + { + writer.WriteAttributeString("DetectCondition", package.ExePackage.DetectCondition); + writer.WriteAttributeString("InstallArguments", package.ExePackage.InstallCommand); + writer.WriteAttributeString("UninstallArguments", package.ExePackage.UninstallCommand); + writer.WriteAttributeString("RepairArguments", package.ExePackage.RepairCommand); + writer.WriteAttributeString("Repairable", package.ExePackage.Repairable ? "yes" : "no"); + if (!String.IsNullOrEmpty(package.ExePackage.ExeProtocol)) + { + writer.WriteAttributeString("Protocol", package.ExePackage.ExeProtocol); + } + } + else if (WixBundlePackageType.Msi == package.Package.Type) + { + writer.WriteAttributeString("ProductCode", package.MsiPackage.ProductCode); + writer.WriteAttributeString("Language", package.MsiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Version", package.MsiPackage.ProductVersion); + writer.WriteAttributeString("DisplayInternalUI", package.MsiPackage.DisplayInternalUI ? "yes" : "no"); + if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", package.MsiPackage.UpgradeCode); + } + } + else if (WixBundlePackageType.Msp == package.Package.Type) + { + writer.WriteAttributeString("PatchCode", package.MspPackage.PatchCode); + writer.WriteAttributeString("PatchXml", package.MspPackage.PatchXml); + writer.WriteAttributeString("DisplayInternalUI", package.MspPackage.DisplayInternalUI ? "yes" : "no"); + + // If there is still a chance that all of our patches will target a narrow set of + // product codes, add the patch list to the overall list. + if (null != targetCodes) + { + if (!package.MspPackage.TargetUnspecified) + { + IEnumerable patchTargetCodes = targetCodesByPatch[package.MspPackage.ChainPackageId]; + + targetCodes.AddRange(patchTargetCodes); + } + else // we have a patch that targets the world, so throw the whole list away. + { + targetCodes = null; + } + } + } + else if (WixBundlePackageType.Msu == package.Package.Type) + { + writer.WriteAttributeString("DetectCondition", package.MsuPackage.DetectCondition); + writer.WriteAttributeString("KB", package.MsuPackage.MsuKB); + } + + IEnumerable packageMsiFeatures = msiFeaturesByPackage[package.Package.WixChainItemId]; + + foreach (WixBundleMsiFeatureRow feature in packageMsiFeatures) + { + writer.WriteStartElement("MsiFeature"); + writer.WriteAttributeString("Id", feature.Name); + writer.WriteEndElement(); + } + + IEnumerable packageMsiProperties = msiPropertiesByPackage[package.Package.WixChainItemId]; + + foreach (WixBundleMsiPropertyRow msiProperty in packageMsiProperties) + { + writer.WriteStartElement("MsiProperty"); + writer.WriteAttributeString("Id", msiProperty.Name); + writer.WriteAttributeString("Value", msiProperty.Value); + if (!String.IsNullOrEmpty(msiProperty.Condition)) + { + writer.WriteAttributeString("Condition", msiProperty.Condition); + } + writer.WriteEndElement(); + } + + IEnumerable packageSlipstreamMsps = slipstreamMspsByPackage[package.Package.WixChainItemId]; + + foreach (WixBundleSlipstreamMspRow slipstreamMsp in packageSlipstreamMsps) + { + writer.WriteStartElement("SlipstreamMsp"); + writer.WriteAttributeString("Id", slipstreamMsp.MspPackageId); + writer.WriteEndElement(); + } + + IEnumerable packageExitCodes = exitCodesByPackage[package.Package.WixChainItemId]; + + foreach (WixBundlePackageExitCodeRow exitCode in packageExitCodes) + { + writer.WriteStartElement("ExitCode"); + + if (exitCode.Code.HasValue) + { + writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture)); + } + else + { + writer.WriteAttributeString("Code", "*"); + } + + writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture)); + writer.WriteEndElement(); + } + + IEnumerable packageCommandLines = commandLinesByPackage[package.Package.WixChainItemId]; + + foreach (WixBundlePackageCommandLineRow commandLine in packageCommandLines) + { + writer.WriteStartElement("CommandLine"); + writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); + writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument); + writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument); + writer.WriteAttributeString("Condition", commandLine.Condition); + writer.WriteEndElement(); + } + + // Output the dependency information. + foreach (ProvidesDependency dependency in package.Provides) + { + // TODO: Add to wixpdb as an imported table, or link package wixpdbs to bundle wixpdbs. + dependency.WriteXml(writer); + } + + IEnumerable packageRelatedPackages = relatedPackagesByPackage[package.Package.WixChainItemId]; + + foreach (WixBundleRelatedPackageRow related in packageRelatedPackages) + { + writer.WriteStartElement("RelatedPackage"); + writer.WriteAttributeString("Id", related.Id); + if (!String.IsNullOrEmpty(related.MinVersion)) + { + writer.WriteAttributeString("MinVersion", related.MinVersion); + writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no"); + } + if (!String.IsNullOrEmpty(related.MaxVersion)) + { + writer.WriteAttributeString("MaxVersion", related.MaxVersion); + writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no"); + } + writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); + + string[] relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (0 < relatedLanguages.Length) + { + writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no"); + foreach (string language in relatedLanguages) + { + writer.WriteStartElement("Language"); + writer.WriteAttributeString("Id", language); + writer.WriteEndElement(); + } + } + writer.WriteEndElement(); + } + + // Write any contained Payloads with the PackagePayload being first + writer.WriteStartElement("PayloadRef"); + writer.WriteAttributeString("Id", package.Package.PackagePayload); + writer.WriteEndElement(); + + IEnumerable packagePayloads = payloadsByPackage[package.Package.WixChainItemId]; + + foreach (WixBundlePayloadRow payload in packagePayloads) + { + if (payload.Id != package.Package.PackagePayload) + { + writer.WriteStartElement("PayloadRef"); + writer.WriteAttributeString("Id", payload.Id); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); // + } + writer.WriteEndElement(); // + + if (null != targetCodes) + { + foreach (WixBundlePatchTargetCodeRow targetCode in targetCodes) + { + writer.WriteStartElement("PatchTargetCode"); + writer.WriteAttributeString("TargetCode", targetCode.TargetCode); + writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); + writer.WriteEndElement(); + } + } + + // Write the ApprovedExeForElevation elements. + IEnumerable approvedExesForElevation = this.Output.Tables["WixApprovedExeForElevation"].RowsAs(); + + foreach (WixApprovedExeForElevationRow approvedExeForElevation in approvedExesForElevation) + { + writer.WriteStartElement("ApprovedExeForElevation"); + writer.WriteAttributeString("Id", approvedExeForElevation.Id); + writer.WriteAttributeString("Key", approvedExeForElevation.Key); + + if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) + { + writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName); + } + + if (approvedExeForElevation.Win64) + { + writer.WriteAttributeString("Win64", "yes"); + } + + writer.WriteEndElement(); + } + + writer.WriteEndDocument(); // + } + } + + private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerRow container) + { + writer.WriteAttributeString("Id", container.Id); + writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", container.Hash); + + if (ContainerType.Detached == container.Type) + { + string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id, container.Name); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + writer.WriteAttributeString("DownloadUrl", resolvedUrl); + } + else if (!String.IsNullOrEmpty(container.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); + } + + writer.WriteAttributeString("FilePath", container.Name); + } + else if (ContainerType.Attached == container.Type) + { + if (!String.IsNullOrEmpty(container.DownloadUrl)) + { + Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id)); + } + + writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. + writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Attached", "yes"); + writer.WriteAttributeString("Primary", "yes"); + } + } + + private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadRow payload, bool embeddedOnly, Dictionary allPayloads) + { + Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging); + + writer.WriteAttributeString("Id", payload.Id); + writer.WriteAttributeString("FilePath", payload.Name); + writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", payload.Hash); + + if (payload.LayoutOnly) + { + writer.WriteAttributeString("LayoutOnly", "yes"); + } + + if (!String.IsNullOrEmpty(payload.PublicKey)) + { + writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.PublicKey); + } + + if (!String.IsNullOrEmpty(payload.Thumbprint)) + { + writer.WriteAttributeString("CertificateRootThumbprint", payload.Thumbprint); + } + + switch (payload.Packaging) + { + case PackagingType.Embedded: // this means it's in a container. + if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id)); + } + + writer.WriteAttributeString("Packaging", "embedded"); + writer.WriteAttributeString("SourcePath", payload.EmbeddedId); + + if (Compiler.BurnUXContainerId != payload.Container) + { + writer.WriteAttributeString("Container", payload.Container); + } + break; + + case PackagingType.External: + string packageId = payload.ParentPackagePayload; + string parentUrl = payload.ParentPackagePayload == null ? null : allPayloads[payload.ParentPackagePayload].DownloadUrl; + string resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id, payload.Name); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + writer.WriteAttributeString("DownloadUrl", resolvedUrl); + } + else if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); + } + + writer.WriteAttributeString("Packaging", "external"); + writer.WriteAttributeString("SourcePath", payload.Name); + break; + } + + if (!String.IsNullOrEmpty(payload.Catalog)) + { + writer.WriteAttributeString("Catalog", payload.Catalog); + } + } + + private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) + { + string resolved = null; + foreach (var extension in this.BackendExtensions) + { + resolved = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); + if (!String.IsNullOrEmpty(resolved)) + { + break; + } + } + + return resolved; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs new file mode 100644 index 00000000..75379713 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs @@ -0,0 +1,68 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Core.Cab; + using WixToolset.Data; + using WixToolset.Data.Rows; + + /// + /// Creates cabinet files. + /// + internal class CreateContainerCommand + { + public CompressionLevel DefaultCompressionLevel { private get; set; } + + public IEnumerable Payloads { private get; set; } + + public string ManifestFile { private get; set; } + + public string OutputPath { private get; set; } + + public string Hash { get; private set; } + + public long Size { get; private set; } + + public void Execute() + { + int payloadCount = this.Payloads.Count(); // The number of embedded payloads + + if (!String.IsNullOrEmpty(this.ManifestFile)) + { + ++payloadCount; + } + + using (var cab = new WixCreateCab(Path.GetFileName(this.OutputPath), Path.GetDirectoryName(this.OutputPath), payloadCount, 0, 0, this.DefaultCompressionLevel)) + { + // If a manifest was provided always add it as "payload 0" to the container. + if (!String.IsNullOrEmpty(this.ManifestFile)) + { + cab.AddFile(this.ManifestFile, "0"); + } + + foreach (WixBundlePayloadRow payload in this.Payloads) + { + Debug.Assert(PackagingType.Embedded == payload.Packaging); + + Messaging.Instance.OnMessage(WixVerboses.LoadingPayload(payload.FullFileName)); + + cab.AddFile(payload.FullFileName, payload.EmbeddedId); + } + + cab.Complete(); + } + + // Now that the container is created, set the outputs of the command. + FileInfo fileInfo = new FileInfo(this.OutputPath); + + this.Hash = Common.GetFileHash(fileInfo.FullName); + + this.Size = fileInfo.Length; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs new file mode 100644 index 00000000..7485758c --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs @@ -0,0 +1,62 @@ +// 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.Burn.Bundles +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class GetPackageFacadesCommand + { + public Table PackageTable { private get; set; } + + public Table ExePackageTable { private get; set; } + + public Table MsiPackageTable { private get; set; } + + public Table MspPackageTable { private get; set; } + + public Table MsuPackageTable { private get; set; } + + public IDictionary PackageFacades { get; private set; } + + public void Execute() + { + RowDictionary exePackages = new RowDictionary(this.ExePackageTable); + RowDictionary msiPackages = new RowDictionary(this.MsiPackageTable); + RowDictionary mspPackages = new RowDictionary(this.MspPackageTable); + RowDictionary msuPackages = new RowDictionary(this.MsuPackageTable); + + Dictionary facades = new Dictionary(this.PackageTable.Rows.Count); + + foreach (WixBundlePackageRow package in this.PackageTable.Rows) + { + string id = package.WixChainItemId; + PackageFacade facade = null; + + switch (package.Type) + { + case WixBundlePackageType.Exe: + facade = new PackageFacade(package, exePackages.Get(id)); + break; + + case WixBundlePackageType.Msi: + facade = new PackageFacade(package, msiPackages.Get(id)); + break; + + case WixBundlePackageType.Msp: + facade = new PackageFacade(package, mspPackages.Get(id)); + break; + + case WixBundlePackageType.Msu: + facade = new PackageFacade(package, msuPackages.Get(id)); + break; + } + + facades.Add(id, facade); + } + + this.PackageFacades = facades; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs new file mode 100644 index 00000000..cb6e2748 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs @@ -0,0 +1,145 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class OrderPackagesAndRollbackBoundariesCommand + { + public Table WixGroupTable { private get; set; } + + public RowDictionary Boundaries { private get; set; } + + public IDictionary PackageFacades { private get; set; } + + public IEnumerable OrderedPackageFacades { get; private set; } + + public IEnumerable UsedRollbackBoundaries { get; private set; } + + public void Execute() + { + List orderedFacades = new List(); + List usedBoundaries = new List(); + + // Process the chain of packages to add them in the correct order + // and assign the forward rollback boundaries as appropriate. Remember + // rollback boundaries are authored as elements in the chain which + // we re-interpret here to add them as attributes on the next available + // package in the chain. Essentially we mark some packages as being + // the start of a rollback boundary when installing and repairing. + // We handle uninstall (aka: backwards) rollback boundaries after + // we get these install/repair (aka: forward) rollback boundaries + // defined. + WixBundleRollbackBoundaryRow previousRollbackBoundary = null; + WixBundleRollbackBoundaryRow lastRollbackBoundary = null; + bool boundaryHadX86Package = false; + + foreach (WixGroupRow row in this.WixGroupTable.Rows) + { + if (ComplexReferenceChildType.Package == row.ChildType && ComplexReferenceParentType.PackageGroup == row.ParentType && "WixChain" == row.ParentId) + { + PackageFacade facade = null; + if (PackageFacades.TryGetValue(row.ChildId, out facade)) + { + if (null != previousRollbackBoundary) + { + usedBoundaries.Add(previousRollbackBoundary); + facade.Package.RollbackBoundary = previousRollbackBoundary.ChainPackageId; + previousRollbackBoundary = null; + + boundaryHadX86Package = (facade.Package.x64 == YesNoType.Yes); + } + + // Error if MSI transaction has x86 package preceding x64 packages + if ((lastRollbackBoundary != null) && (lastRollbackBoundary.Transaction == YesNoType.Yes) + && boundaryHadX86Package + && (facade.Package.x64 == YesNoType.Yes)) + { + Messaging.Instance.OnMessage(WixErrors.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); + } + boundaryHadX86Package = boundaryHadX86Package || (facade.Package.x64 == YesNoType.No); + + orderedFacades.Add(facade); + } + else // must be a rollback boundary. + { + // Discard the next rollback boundary if we have a previously defined boundary. + WixBundleRollbackBoundaryRow nextRollbackBoundary = Boundaries.Get(row.ChildId); + if (null != previousRollbackBoundary) + { + Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.ChainPackageId)); + } + else + { + previousRollbackBoundary = nextRollbackBoundary; + lastRollbackBoundary = nextRollbackBoundary; + } + } + } + } + + if (null != previousRollbackBoundary) + { + Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.ChainPackageId)); + } + + // With the forward rollback boundaries assigned, we can now go + // through the packages with rollback boundaries and assign backward + // rollback boundaries. Backward rollback boundaries are used when + // the chain is going "backwards" which (AFAIK) only happens during + // uninstall. + // + // Consider the scenario with three packages: A, B and C. Packages A + // and C are marked as rollback boundary packages and package B is + // not. The naive implementation would execute the chain like this + // (numbers indicate where rollback boundaries would end up): + // install: 1 A B 2 C + // uninstall: 2 C B 1 A + // + // The uninstall chain is wrong, A and B should be grouped together + // not C and B. The fix is to label packages with a "backwards" + // rollback boundary used during uninstall. The backwards rollback + // boundaries are assigned to the package *before* the next rollback + // boundary. Using our example from above again, I'll mark the + // backwards rollback boundaries prime (aka: with '). + // install: 1 A B 1' 2 C 2' + // uninstall: 2' C 2 1' B A 1 + // + // If the marked boundaries are ignored during install you get the + // same thing as above (good) and if the non-marked boundaries are + // ignored during uninstall then A and B are correctly grouped. + // Here's what it looks like without all the markers: + // install: 1 A B 2 C + // uninstall: 2 C 1 B A + // Woot! + string previousRollbackBoundaryId = null; + PackageFacade previousFacade = null; + + foreach (PackageFacade package in orderedFacades) + { + if (null != package.Package.RollbackBoundary) + { + if (null != previousFacade) + { + previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; + } + + previousRollbackBoundaryId = package.Package.RollbackBoundary; + } + + previousFacade = package; + } + + if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) + { + previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; + } + + this.OrderedPackageFacades = orderedFacades; + this.UsedRollbackBoundaries = usedBoundaries; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs new file mode 100644 index 00000000..3f2e184d --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs @@ -0,0 +1,58 @@ +// 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.Burn.Bundles +{ + using WixToolset.Data.Rows; + + internal class PackageFacade + { + private PackageFacade(WixBundlePackageRow package) + { + this.Package = package; + this.Provides = new ProvidesDependencyCollection(); + } + + public PackageFacade(WixBundlePackageRow package, WixBundleExePackageRow exePackage) + : this(package) + { + this.ExePackage = exePackage; + } + + public PackageFacade(WixBundlePackageRow package, WixBundleMsiPackageRow msiPackage) + : this(package) + { + this.MsiPackage = msiPackage; + } + + public PackageFacade(WixBundlePackageRow package, WixBundleMspPackageRow mspPackage) + : this(package) + { + this.MspPackage = mspPackage; + } + + public PackageFacade(WixBundlePackageRow package, WixBundleMsuPackageRow msuPackage) + : this(package) + { + this.MsuPackage = msuPackage; + } + + public WixBundlePackageRow Package { get; private set; } + + public WixBundleExePackageRow ExePackage { get; private set; } + + public WixBundleMsiPackageRow MsiPackage { get; private set; } + + public WixBundleMspPackageRow MspPackage { get; private set; } + + public WixBundleMsuPackageRow MsuPackage { get; private set; } + + /// + /// The provides dependencies authored and imported for this package. + /// + /// + /// TODO: Eventually this collection should turn into Rows so they are tracked in the PDB but + /// the relationship with the extension makes it much trickier to pull off. + /// + public ProvidesDependencyCollection Provides { get; private set; } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs new file mode 100644 index 00000000..11512c39 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs @@ -0,0 +1,33 @@ +// 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.Burn.Bundles +{ + using System; + using WixToolset.Data; + using WixToolset.Data.Rows; + + /// + /// Initializes package state from the Exe contents. + /// + internal class ProcessExePackageCommand + { + public RowDictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + /// + /// Processes the Exe packages to add properties and payloads from the Exe packages. + /// + public void Execute() + { + WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + + if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + { + this.Facade.Package.CacheId = packagePayload.Hash; + } + + this.Facade.Package.Version = packagePayload.Version; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs new file mode 100644 index 00000000..322187f9 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -0,0 +1,576 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + using WixToolset.Core.Native; + using Dtf = WixToolset.Dtf.WindowsInstaller; + using WixToolset.Bind; + using WixToolset.Data.Bind; + + /// + /// Initializes package state from the MSI contents. + /// + internal class ProcessMsiPackageCommand + { + private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"; + + public RowDictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public IEnumerable BackendExtensions { private get; set; } + + public Table MsiFeatureTable { private get; set; } + + public Table MsiPropertyTable { private get; set; } + + public Table PayloadTable { private get; set; } + + public Table RelatedPackageTable { private get; set; } + + /// + /// Processes the MSI packages to add properties and payloads from the MSI packages. + /// + public void Execute() + { + WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + + string sourcePath = packagePayload.FullFileName; + bool longNamesInImage = false; + bool compressed = false; + bool x64 = false; + try + { + // Read data out of the msi database... + using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) + { + // 1 is the Word Count summary information stream bit that means + // the MSI uses short file names when set. We care about long file + // names so check when the bit is not set. + longNamesInImage = 0 == (sumInfo.WordCount & 1); + + // 2 is the Word Count summary information stream bit that means + // files are compressed in the MSI by default when the bit is set. + compressed = 2 == (sumInfo.WordCount & 2); + + x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64")); + + // 8 is the Word Count summary information stream bit that means + // "Elevated privileges are not required to install this package." + // in MSI 4.5 and below, if this bit is 0, elevation is required. + this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No; + this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No; + } + + using (Dtf.Database db = new Dtf.Database(sourcePath)) + { + this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode"); + this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode"); + this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer"); + this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture); + this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion"); + + if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion)) + { + // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? + string version = null; + string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.'); + int count = versionParts.Length; + if (0 < count) + { + version = versionParts[0]; + for (int i = 1; i < 4 && i < count; ++i) + { + version = String.Concat(version, ".", versionParts[i]); + } + } + + if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version)) + { + Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version)); + this.Facade.MsiPackage.ProductVersion = version; + } + else + { + Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath)); + } + } + + if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + { + this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion); + } + + if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) + { + this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName"); + } + + if (String.IsNullOrEmpty(this.Facade.Package.Description)) + { + this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); + } + + ISet payloadNames = this.GetPayloadTargetNames(); + + ISet msiPropertyNames = this.GetMsiPropertyNames(); + + this.SetPerMachineAppropriately(db, sourcePath); + + // Ensure the MSI package is appropriately marked visible or not. + this.SetPackageVisibility(db, msiPropertyNames); + + // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. + if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL")) + { + this.AddMsiProperty("MSIFASTINSTALL", "7"); + } + + this.CreateRelatedPackages(db); + + // If feature selection is enabled, represent the Feature table in the manifest. + if (this.Facade.MsiPackage.EnableFeatureSelection) + { + this.CreateMsiFeatures(db); + } + + // Add all external cabinets as package payloads. + this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames); + + // Add all external files as package payloads and calculate the total install size as the rollup of + // File table's sizes. + this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); + + // Add all dependency providers from the MSI. + this.ImportDependencyProviders(db); + } + } + catch (Dtf.InstallerException e) + { + Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message)); + } + } + + private ISet GetPayloadTargetNames() + { + IEnumerable payloadNames = this.PayloadTable.RowsAs() + .Where(r => r.Package == this.Facade.Package.WixChainItemId) + .Select(r => r.Name); + + return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); + } + + private ISet GetMsiPropertyNames() + { + IEnumerable properties = this.MsiPropertyTable.RowsAs() + .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId) + .Select(r => r.Name); + + return new HashSet(properties, StringComparer.Ordinal); + } + + private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath) + { + if (this.Facade.MsiPackage.ForcePerMachine) + { + if (YesNoDefaultType.No == this.Facade.Package.PerMachine) + { + Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath)); + this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. + } + + // Force ALLUSERS=1 via the MSI command-line. + this.AddMsiProperty("ALLUSERS", "1"); + } + else + { + string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS"); + + if (String.IsNullOrEmpty(allusers)) + { + // Not forced per-machine and no ALLUSERS property, flip back to per-user. + if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) + { + Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath)); + this.Facade.Package.PerMachine = YesNoDefaultType.No; + } + } + else if (allusers.Equals("1", StringComparison.Ordinal)) + { + if (YesNoDefaultType.No == this.Facade.Package.PerMachine) + { + Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath)); + } + } + else if (allusers.Equals("2", StringComparison.Ordinal)) + { + Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user")); + } + else + { + Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers)); + } + } + } + + private void SetPackageVisibility(Dtf.Database db, ISet msiPropertyNames) + { + bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT"); + + if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility. + { + // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. + if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) + { + this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1"); + } + } + } + + private void CreateRelatedPackages(Dtf.Database db) + { + // Represent the Upgrade table as related packages. + if (db.Tables.Contains("Upgrade")) + { + using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) + { + view.Execute(); + while (true) + { + using (Dtf.Record record = view.Fetch()) + { + if (null == record) + { + break; + } + + WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers); + related.ChainPackageId = this.Facade.Package.WixChainItemId; + related.Id = record.GetString(1); + related.MinVersion = record.GetString(2); + related.MaxVersion = record.GetString(3); + related.Languages = record.GetString(4); + + int attributes = record.GetInteger(5); + related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect; + related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; + related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; + related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0; + } + } + } + } + } + + private void CreateMsiFeatures(Dtf.Database db) + { + if (db.Tables.Contains("Feature")) + { + using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) + using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) + { + using (Dtf.Record featureRecord = new Dtf.Record(1)) + using (Dtf.Record componentRecord = new Dtf.Record(1)) + { + using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) + { + allFeaturesView.Execute(); + + while (true) + { + using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch()) + { + if (null == allFeaturesResultRecord) + { + break; + } + + string featureName = allFeaturesResultRecord.GetString(1); + + // Calculate the Feature size. + featureRecord.SetString(1, featureName); + featureView.Execute(featureRecord); + + // Loop over all the components for the feature to calculate the size of the feature. + long size = 0; + while (true) + { + using (Dtf.Record componentResultRecord = featureView.Fetch()) + { + if (null == componentResultRecord) + { + break; + } + string component = componentResultRecord.GetString(1); + componentRecord.SetString(1, component); + componentView.Execute(componentRecord); + + while (true) + { + using (Dtf.Record fileResultRecord = componentView.Fetch()) + { + if (null == fileResultRecord) + { + break; + } + + string fileSize = fileResultRecord.GetString(1); + size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); + } + } + } + } + + WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers); + feature.ChainPackageId = this.Facade.Package.WixChainItemId; + feature.Name = featureName; + feature.Parent = allFeaturesResultRecord.GetString(2); + feature.Title = allFeaturesResultRecord.GetString(3); + feature.Description = allFeaturesResultRecord.GetString(4); + feature.Display = allFeaturesResultRecord.GetInteger(5); + feature.Level = allFeaturesResultRecord.GetInteger(6); + feature.Directory = allFeaturesResultRecord.GetString(7); + feature.Attributes = allFeaturesResultRecord.GetInteger(8); + feature.Size = size; + } + } + } + } + } + } + } + + private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet payloadNames) + { + if (db.Tables.Contains("Media")) + { + foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) + { + if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) + { + // If we didn't find the Payload as an existing child of the package, we need to + // add it. We expect the file to exist on-disk in the same relative location as + // the MSI expects to find it... + string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); + + if (!payloadNames.Contains(cabinetName)) + { + string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet); + string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal); + + WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); + payload.Id = generatedId; + payload.Name = cabinetName; + payload.SourceFile = payloadSourceFile; + payload.Compressed = packagePayload.Compressed; + payload.UnresolvedSourceFile = cabinetName; + payload.Package = packagePayload.Package; + payload.Container = packagePayload.Container; + payload.ContentFile = true; + payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; + payload.Packaging = packagePayload.Packaging; + payload.ParentPackagePayload = packagePayload.Id; + } + } + } + } + } + + private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) + { + long size = 0; + + if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File")) + { + Hashtable directories = new Hashtable(); + + // Load up the directory hash table so we will be able to resolve source paths + // for files in the MSI database. + using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) + { + view.Execute(); + while (true) + { + using (Dtf.Record record = view.Fetch()) + { + if (null == record) + { + break; + } + + string sourceName = Common.GetName(record.GetString(3), true, longNamesInImage); + directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName)); + } + } + } + + // Resolve the source paths to external files and add each file size to the total + // install size of the package. + using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) + { + view.Execute(); + while (true) + { + using (Dtf.Record record = view.Fetch()) + { + if (null == record) + { + break; + } + + // Skip adding the loose files as payloads if it was suppressed. + if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration) + { + // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not + // explicitly marked compressed then this is an external file. + if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) || + (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed))) + { + string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); + string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); + + if (!payloadNames.Contains(name)) + { + string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2)); + string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal); + + WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); + payload.Id = generatedId; + payload.Name = name; + payload.SourceFile = payloadSourceFile; + payload.Compressed = packagePayload.Compressed; + payload.UnresolvedSourceFile = name; + payload.Package = packagePayload.Package; + payload.Container = packagePayload.Container; + payload.ContentFile = true; + payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; + payload.Packaging = packagePayload.Packaging; + payload.ParentPackagePayload = packagePayload.Id; + } + } + } + + size += record.GetInteger(5); + } + } + } + } + + return size; + } + + private void AddMsiProperty(string name, string value) + { + WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers); + row.ChainPackageId = this.Facade.Package.WixChainItemId; + row.Name = name; + row.Value = value; + } + + private void ImportDependencyProviders(Dtf.Database db) + { + if (db.Tables.Contains("WixDependencyProvider")) + { + string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; + + using (Dtf.View view = db.OpenView(query)) + { + view.Execute(); + while (true) + { + using (Dtf.Record record = view.Fetch()) + { + if (null == record) + { + break; + } + + // Import the provider key and attributes. + string providerKey = record.GetString(1); + string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion; + string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName; + int attributes = record.GetInteger(4); + + ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes); + dependency.Imported = true; + + this.Facade.Provides.Add(dependency); + } + } + } + } + } + + private string ResolveRelatedFile(string sourceFile, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage stage) + { + foreach (var extension in this.BackendExtensions) + { + var relatedFile = extension.ResolveRelatedFile(sourceFile, relatedSource, type, sourceLineNumbers, stage); + + if (!String.IsNullOrEmpty(relatedFile)) + { + return relatedFile; + } + } + + return null; + } + + /// + /// Queries a Windows Installer database for a Property value. + /// + /// Database to query. + /// Property to examine. + /// String value for result or null if query doesn't match a single result. + private static string GetProperty(Dtf.Database db, string property) + { + try + { + return db.ExecuteScalar(PropertyQuery(property)).ToString(); + } + catch (Dtf.InstallerException) + { + } + + return null; + } + + /// + /// Queries a Windows Installer database to determine if one or more rows exist in the Property table. + /// + /// Database to query. + /// Property to examine. + /// True if query matches at least one result. + private static bool HasProperty(Dtf.Database db, string property) + { + try + { + return 0 < db.ExecuteQuery(PropertyQuery(property)).Count; + } + catch (Dtf.InstallerException) + { + } + + return false; + } + + private static string PropertyQuery(string property) + { + // quick sanity check that we'll be creating a valid query... + // TODO: Are there any other special characters we should be looking for? + Debug.Assert(!property.Contains("'")); + + return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs new file mode 100644 index 00000000..2d849d03 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs @@ -0,0 +1,189 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Rows; + using Dtf = WixToolset.Dtf.WindowsInstaller; + + /// + /// Initializes package state from the Msp contents. + /// + internal class ProcessMspPackageCommand + { + private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'"; + private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false); + + public RowDictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public Table WixBundlePatchTargetCodeTable { private get; set; } + + /// + /// Processes the Msp packages to add properties and payloads from the Msp packages. + /// + public void Execute() + { + WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + + string sourcePath = packagePayload.FullFileName; + + try + { + // Read data out of the msp database... + using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) + { + this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); + } + + using (Dtf.Database db = new Dtf.Database(sourcePath)) + { + if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) + { + this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); + } + + if (String.IsNullOrEmpty(this.Facade.Package.Description)) + { + this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); + } + + this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); + } + + this.ProcessPatchXml(packagePayload, sourcePath); + } + catch (Dtf.InstallerException e) + { + Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); + return; + } + + if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + { + this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode; + } + } + + private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath) + { + HashSet uniqueTargetCodes = new HashSet(); + + string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(patchXml); + + XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); + + // Determine target ProductCodes and/or UpgradeCodes. + foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) + { + // If this patch targets a product code, this is the best case. + XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); + WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; + + if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) + { + attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; + } + else // maybe targets an upgrade code? + { + targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); + if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) + { + attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; + } + else // this patch targets an unknown number of products + { + this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; + } + } + + string targetCode = targetCodeElement.InnerText; + + if (uniqueTargetCodes.Add(targetCode)) + { + WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers); + row.MspPackageId = packagePayload.Id; + row.TargetCode = targetCode; + row.Attributes = attributes; + } + } + + // Suppress patch sequence data for improved performance. + XmlNode root = doc.DocumentElement; + foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) + { + root.RemoveChild(node); + } + + // Save the XML as compact as possible. + using (StringWriter writer = new StringWriter()) + { + XmlWriterSettings settings = new XmlWriterSettings() + { + Encoding = ProcessMspPackageCommand.XmlOutputEncoding, + Indent = false, + NewLineChars = string.Empty, + NewLineHandling = NewLineHandling.Replace, + }; + + using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) + { + doc.WriteTo(xmlWriter); + } + + this.Facade.MspPackage.PatchXml = writer.ToString(); + } + } + + /// + /// Queries a Windows Installer patch database for a Property value from the MsiPatchMetadata table. + /// + /// Database to query. + /// Property to examine. + /// String value for result or null if query doesn't match a single result. + private static string GetPatchMetadataProperty(Dtf.Database db, string property) + { + try + { + return db.ExecuteScalar(PatchMetadataPropertyQuery(property)).ToString(); + } + catch (Dtf.InstallerException) + { + } + + return null; + } + + private static string PatchMetadataPropertyQuery(string property) + { + // quick sanity check that we'll be creating a valid query... + // TODO: Are there any other special characters we should be looking for? + Debug.Assert(!property.Contains("'")); + + return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property); + } + + private static bool TargetsCode(XmlNode node) + { + if (null != node) + { + XmlAttribute attr = node.Attributes["Validate"]; + return null != attr && "true".Equals(attr.Value); + } + + return false; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs new file mode 100644 index 00000000..fcfc780c --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs @@ -0,0 +1,30 @@ +// 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.Burn.Bundles +{ + using System; + using WixToolset.Data; + using WixToolset.Data.Rows; + + /// + /// Processes the Msu packages to add properties and payloads from the Msu packages. + /// + internal class ProcessMsuPackageCommand + { + public RowDictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public void Execute() + { + WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + + if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + { + this.Facade.Package.CacheId = packagePayload.Hash; + } + + this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs new file mode 100644 index 00000000..5dbd6aaa --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -0,0 +1,161 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.Text; + using WixToolset.Bind; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Data.Rows; + + internal class ProcessPayloadsCommand + { + private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); + + public IEnumerable Payloads { private get; set; } + + public PackagingType DefaultPackaging { private get; set; } + + public string LayoutDirectory { private get; set; } + + public IEnumerable FileTransfers { get; private set; } + + public void Execute() + { + List fileTransfers = new List(); + + foreach (WixBundlePayloadRow payload in this.Payloads) + { + string normalizedPath = payload.Name.Replace('\\', '/'); + if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) + { + Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); + } + + // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden + // in the .wixlib). + ObjectField field = (ObjectField)payload.Fields[2]; + payload.ContentFile = !field.EmbeddedFileIndex.HasValue; + + this.UpdatePayloadPackagingType(payload); + + if (String.IsNullOrEmpty(payload.SourceFile)) + { + // Remote payloads obviously cannot be embedded. + Debug.Assert(PackagingType.Embedded != payload.Packaging); + } + else // not a remote payload so we have a lot more to update. + { + this.UpdatePayloadFileInformation(payload); + + this.UpdatePayloadVersionInformation(payload); + + // External payloads need to be transfered. + if (PackagingType.External == payload.Packaging) + { + FileTransfer transfer; + if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer)) + { + fileTransfers.Add(transfer); + } + } + } + } + + this.FileTransfers = fileTransfers; + } + + private void UpdatePayloadPackagingType(WixBundlePayloadRow payload) + { + if (PackagingType.Unknown == payload.Packaging) + { + if (YesNoDefaultType.Yes == payload.Compressed) + { + payload.Packaging = PackagingType.Embedded; + } + else if (YesNoDefaultType.No == payload.Compressed) + { + payload.Packaging = PackagingType.External; + } + else + { + payload.Packaging = this.DefaultPackaging; + } + } + + // Embedded payloads that are not assigned a container already are placed in the default attached + // container. + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container)) + { + payload.Container = Compiler.BurnDefaultAttachedContainerId; + } + } + + private void UpdatePayloadFileInformation(WixBundlePayloadRow payload) + { + FileInfo fileInfo = new FileInfo(payload.SourceFile); + + if (null != fileInfo) + { + payload.FileSize = (int)fileInfo.Length; + + payload.Hash = Common.GetFileHash(fileInfo.FullName); + + // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation. + if (payload.EnableSignatureValidation) + { + X509Certificate2 certificate = null; + try + { + certificate = new X509Certificate2(fileInfo.FullName); + } + catch (CryptographicException) // we don't care about non-signed files. + { + } + + // If there is a certificate, remember its hashed public key identifier and thumbprint. + if (null != certificate) + { + byte[] publicKeyIdentifierHash = new byte[128]; + uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length; + + WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); + StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); + for (int i = 0; i < publicKeyIdentifierHashSize; ++i) + { + sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]); + } + + payload.PublicKey = sb.ToString(); + payload.Thumbprint = certificate.Thumbprint; + } + } + } + } + + private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload) + { + FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile); + + if (null != versionInfo) + { + // Use the fixed version info block for the file since the resource text may not be a dotted quad. + Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); + + if (ProcessPayloadsCommand.EmptyVersion != version) + { + payload.Version = version.ToString(); + } + + payload.Description = versionInfo.FileDescription; + payload.DisplayName = versionInfo.ProductName; + } + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs new file mode 100644 index 00000000..9919f777 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs @@ -0,0 +1,148 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class VerifyPayloadsWithCatalogCommand + { + public IEnumerable Catalogs { private get; set; } + + public IEnumerable Payloads { private get; set; } + + public void Execute() + { + List catalogIdsWithPaths = this.Catalogs + .Join(this.Payloads, + catalog => catalog.Payload, + payload => payload.Id, + (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id, FullPath = Path.GetFullPath(payload.SourceFile) }) + .ToList(); + + foreach (WixBundlePayloadRow payloadInfo in this.Payloads) + { + // Payloads that are not embedded should be verfied. + if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) + { + bool validated = false; + + foreach (CatalogIdWithPath catalog in catalogIdsWithPaths) + { + if (!validated) + { + // Get the file hash + uint cryptHashSize = 20; + byte[] cryptHashBytes = new byte[cryptHashSize]; + int error; + IntPtr fileHandle = IntPtr.Zero; + using (FileStream payloadStream = File.OpenRead(payloadInfo.FullFileName)) + { + // Get the file handle + fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); + + // 20 bytes is usually the hash size. Future hashes may be bigger + if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) + { + error = Marshal.GetLastWin32Error(); + + if (VerifyInterop.ErrorInsufficientBuffer == error) + { + error = 0; + cryptHashBytes = new byte[cryptHashSize]; + if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) + { + error = Marshal.GetLastWin32Error(); + } + } + + if (0 != error) + { + Messaging.Instance.OnMessage(WixErrors.CatalogFileHashFailed(payloadInfo.FullFileName, error)); + } + } + } + + VerifyInterop.WinTrustCatalogInfo catalogData = new VerifyInterop.WinTrustCatalogInfo(); + VerifyInterop.WinTrustData trustData = new VerifyInterop.WinTrustData(); + try + { + // Create WINTRUST_CATALOG_INFO structure + catalogData.cbStruct = (uint)Marshal.SizeOf(catalogData); + catalogData.cbCalculatedFileHash = cryptHashSize; + catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize); + Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize); + + StringBuilder hashString = new StringBuilder(); + foreach (byte hashByte in cryptHashBytes) + { + hashString.Append(hashByte.ToString("X2")); + } + catalogData.pcwszMemberTag = hashString.ToString(); + + // The file names need to be lower case for older OSes + catalogData.pcwszMemberFilePath = payloadInfo.FullFileName.ToLowerInvariant(); + catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant(); + + // Create WINTRUST_DATA structure + trustData.cbStruct = (uint)Marshal.SizeOf(trustData); + trustData.dwUIChoice = VerifyInterop.WTD_UI_NONE; + trustData.fdwRevocationChecks = VerifyInterop.WTD_REVOKE_NONE; + trustData.dwUnionChoice = VerifyInterop.WTD_CHOICE_CATALOG; + trustData.dwStateAction = VerifyInterop.WTD_STATEACTION_VERIFY; + trustData.dwProvFlags = VerifyInterop.WTD_REVOCATION_CHECK_NONE; + + // Create the structure pointers for unmanaged + trustData.pCatalog = Marshal.AllocCoTaskMem(Marshal.SizeOf(catalogData)); + Marshal.StructureToPtr(catalogData, trustData.pCatalog, false); + + // Call WinTrustVerify to validate the file with the catalog + IntPtr noWindow = new IntPtr(-1); + Guid verifyGuid = new Guid(VerifyInterop.GenericVerify2); + long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData); + if (0 == verifyResult) + { + payloadInfo.Catalog = catalog.Id; + validated = true; + break; + } + } + finally + { + // Free the structure memory + if (IntPtr.Zero != trustData.pCatalog) + { + Marshal.FreeCoTaskMem(trustData.pCatalog); + } + + if (IntPtr.Zero != catalogData.pbCalculatedFileHash) + { + Marshal.FreeCoTaskMem(catalogData.pbCalculatedFileHash); + } + } + } + } + + // Error message if the file was not validated by one of the catalogs + if (!validated) + { + Messaging.Instance.OnMessage(WixErrors.CatalogVerificationFailed(payloadInfo.FullFileName)); + } + } + } + } + + private class CatalogIdWithPath + { + public string Id { get; set; } + + public string FullPath { get; set; } + } + } +} diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs new file mode 100644 index 00000000..5eb76479 --- /dev/null +++ b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs @@ -0,0 +1,53 @@ +// 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.Burn.Inscribe +{ + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility; + + internal class InscribeBundleCommand + { + public InscribeBundleCommand(IInscribeContext context) + { + this.Context = context; + } + + private IInscribeContext Context { get; } + + public bool Execute() + { + bool inscribed = false; + string tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); + + using (BurnReader reader = BurnReader.Open(this.Context.InputFilePath)) + { + File.Copy(this.Context.SignedEngineFile, tempFile, true); + + // If there was an attached container on the original (unsigned) bundle, put it back. + if (reader.AttachedContainerSize > 0) + { + reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); + + using (BurnWriter writer = BurnWriter.Open(tempFile)) + { + writer.RememberThenResetSignature(); + writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); + inscribed = true; + } + } + } + + Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); + if (File.Exists(this.Context.OutputFile)) + { + File.Delete(this.Context.OutputFile); + } + + File.Move(tempFile, this.Context.OutputFile); + WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { this.Context.OutputFile }, 1); + + return inscribed; + } + } +} diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs new file mode 100644 index 00000000..26af056b --- /dev/null +++ b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs @@ -0,0 +1,61 @@ +// 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.Burn.Inscribe +{ + using System; + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility; + + internal class InscribeBundleEngineCommand + { + public InscribeBundleEngineCommand(IInscribeContext context) + { + this.Context = context; + } + + private IInscribeContext Context { get; } + + public bool Execute() + { + string tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_unsigned.exe"); + + using (BurnReader reader = BurnReader.Open(this.Context.InputFilePath)) + using (FileStream writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) + { + reader.Stream.Seek(0, SeekOrigin.Begin); + + byte[] buffer = new byte[4 * 1024]; + int total = 0; + int read = 0; + do + { + read = Math.Min(buffer.Length, (int)reader.EngineSize - total); + + read = reader.Stream.Read(buffer, 0, read); + writer.Write(buffer, 0, read); + + total += read; + } while (total < reader.EngineSize && 0 < read); + + if (total != reader.EngineSize) + { + throw new InvalidOperationException("Failed to copy engine out of bundle."); + } + + // TODO: update writer with detached container signatures. + } + + Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); + if (File.Exists(this.Context.OutputFile)) + { + File.Delete(this.Context.OutputFile); + } + + File.Move(tempFile, this.Context.OutputFile); + WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { this.Context.OutputFile }, 1); + + return true; + } + } +} diff --git a/src/WixToolset.Core.Burn/VerifyInterop.cs b/src/WixToolset.Core.Burn/VerifyInterop.cs new file mode 100644 index 00000000..81fbec65 --- /dev/null +++ b/src/WixToolset.Core.Burn/VerifyInterop.cs @@ -0,0 +1,68 @@ +// 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 +{ + using System; + using System.Collections; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + internal class VerifyInterop + { + internal const string GenericVerify2 = "00AAC56B-CD44-11d0-8CC2-00C04FC295EE"; + internal const uint WTD_UI_NONE = 2; + internal const uint WTD_REVOKE_NONE = 0; + internal const uint WTD_CHOICE_CATALOG = 2; + internal const uint WTD_STATEACTION_VERIFY = 1; + internal const uint WTD_REVOCATION_CHECK_NONE = 0x10; + internal const int ErrorInsufficientBuffer = 122; + + [StructLayout(LayoutKind.Sequential)] + internal struct WinTrustData + { + internal uint cbStruct; + internal IntPtr pPolicyCallbackData; + internal IntPtr pSIPClientData; + internal uint dwUIChoice; + internal uint fdwRevocationChecks; + internal uint dwUnionChoice; + internal IntPtr pCatalog; + internal uint dwStateAction; + internal IntPtr hWVTStateData; + [MarshalAs(UnmanagedType.LPWStr)] + internal string pwszURLReference; + internal uint dwProvFlags; + internal uint dwUIContext; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct WinTrustCatalogInfo + { + internal uint cbStruct; + internal uint dwCatalogVersion; + [MarshalAs(UnmanagedType.LPWStr)] + internal string pcwszCatalogFilePath; + [MarshalAs(UnmanagedType.LPWStr)] + internal string pcwszMemberTag; + [MarshalAs(UnmanagedType.LPWStr)] + internal string pcwszMemberFilePath; + internal IntPtr hMemberFile; + internal IntPtr pbCalculatedFileHash; + internal uint cbCalculatedFileHash; + internal IntPtr pcCatalogContext; + } + + [DllImport("wintrust.dll", SetLastError = true)] + internal static extern long WinVerifyTrust(IntPtr windowHandle, ref Guid actionGuid, ref WinTrustData trustData); + + [DllImport("wintrust.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptCATAdminCalcHashFromFileHandle( + IntPtr fileHandle, + [In, Out] + ref uint hashSize, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] + byte[] hashBytes, + uint flags); + } +} diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj new file mode 100644 index 00000000..878ac200 --- /dev/null +++ b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -0,0 +1,36 @@ + + + + + + netstandard2.0 + Core Burn + WiX Toolset Core Burn + + + + NU1701 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs new file mode 100644 index 00000000..23c481b7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs @@ -0,0 +1,314 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Rows; + + /// + /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. + /// + public class AssignMediaCommand + { + public AssignMediaCommand() + { + this.CabinetNameTemplate = "Cab{0}.cab"; + } + + public Output Output { private get; set; } + + public bool FilesCompressed { private get; set; } + + public string CabinetNameTemplate { private get; set; } + + public IEnumerable FileFacades { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + /// + /// Gets cabinets with their file rows. + /// + public Dictionary> FileFacadesByCabinetMedia { get; private set; } + + /// + /// Get media rows. + /// + public RowDictionary MediaRows { get; private set; } + + /// + /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. + /// This contains all the files when Package element is marked with compression=no + /// + public IEnumerable UncompressedFileFacades { get; private set; } + + public void Execute() + { + Dictionary> filesByCabinetMedia = new Dictionary>(); + + RowDictionary mediaRows = new RowDictionary(); + + List uncompressedFiles = new List(); + + MediaRow mergeModuleMediaRow = null; + Table mediaTable = this.Output.Tables["Media"]; + Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; + + // If both tables are authored, it is an error. + if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1)) + { + throw new WixException(WixErrors.MediaTableCollision(null)); + } + + // When building merge module, all the files go to "#MergeModule.CABinet". + if (OutputType.Module == this.Output.Type) + { + Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]); + mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null); + mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet"; + + filesByCabinetMedia.Add(mergeModuleMediaRow, new List()); + } + + if (OutputType.Module == this.Output.Type || null == mediaTemplateTable) + { + this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); + } + else + { + this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); + } + + this.FileFacadesByCabinetMedia = new Dictionary>(); + + foreach (var mediaRowWithFiles in filesByCabinetMedia) + { + this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value); + } + + this.MediaRows = mediaRows; + + this.UncompressedFileFacades = uncompressedFiles; + } + + /// + /// Assign files to cabinets based on MediaTemplate authoring. + /// + /// FileRowCollection + private void AutoAssignFiles(Table mediaTable, IEnumerable fileFacades, Dictionary> filesByCabinetMedia, RowDictionary mediaRows, List uncompressedFiles) + { + const int MaxCabIndex = 999; + + ulong currentPreCabSize = 0; + ulong maxPreCabSizeInBytes; + int maxPreCabSizeInMB = 0; + int currentCabIndex = 0; + + MediaRow currentMediaRow = null; + + Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; + + // Auto assign files to cabinets based on maximum uncompressed media size + mediaTable.Rows.Clear(); + WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; + + if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) + { + this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; + } + + string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + + try + { + // Override authored mums value if environment variable is authored. + if (!String.IsNullOrEmpty(mumsString)) + { + maxPreCabSizeInMB = Int32.Parse(mumsString); + } + else + { + maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; + } + + maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; + } + catch (FormatException) + { + throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); + } + catch (OverflowException) + { + throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); + } + + foreach (FileFacade facade in this.FileFacades) + { + // When building a product, if the current file is not to be compressed or if + // the package set not to be compressed, don't cab it. + if (OutputType.Product == this.Output.Type && + (YesNoType.No == facade.File.Compressed || + (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) + { + uncompressedFiles.Add(facade); + continue; + } + + if (currentCabIndex == MaxCabIndex) + { + // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. + List cabinetFiles = filesByCabinetMedia[currentMediaRow]; + facade.WixFile.DiskId = currentCabIndex; + cabinetFiles.Add(facade); + continue; + } + + // Update current cab size. + currentPreCabSize += (ulong)facade.File.FileSize; + + if (currentPreCabSize > maxPreCabSizeInBytes) + { + // Overflow due to current file + currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); + mediaRows.Add(currentMediaRow); + filesByCabinetMedia.Add(currentMediaRow, new List()); + + List cabinetFileRows = filesByCabinetMedia[currentMediaRow]; + facade.WixFile.DiskId = currentCabIndex; + cabinetFileRows.Add(facade); + // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize + currentPreCabSize = (ulong)facade.File.FileSize; + } + else + { + // File fits in the current cab. + if (currentMediaRow == null) + { + // Create new cab and MediaRow + currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); + mediaRows.Add(currentMediaRow); + filesByCabinetMedia.Add(currentMediaRow, new List()); + } + + // Associate current file with current cab. + List cabinetFiles = filesByCabinetMedia[currentMediaRow]; + facade.WixFile.DiskId = currentCabIndex; + cabinetFiles.Add(facade); + } + } + + // If there are uncompressed files and no MediaRow, create a default one. + if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0) + { + MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null); + defaultMediaRow.DiskId = 1; + mediaRows.Add(defaultMediaRow); + } + } + + /// + /// Assign files to cabinets based on Media authoring. + /// + /// + /// + /// + private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable fileFacades, Dictionary> filesByCabinetMedia, RowDictionary mediaRows, List uncompressedFiles) + { + if (OutputType.Module != this.Output.Type) + { + if (null != mediaTable) + { + Dictionary cabinetMediaRows = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + foreach (MediaRow mediaRow in mediaTable.Rows) + { + // If the Media row has a cabinet, make sure it is unique across all Media rows. + if (!String.IsNullOrEmpty(mediaRow.Cabinet)) + { + MediaRow existingRow; + if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow)) + { + Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); + Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); + } + else + { + cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); + } + } + + mediaRows.Add(mediaRow); + } + } + + foreach (MediaRow mediaRow in mediaRows.Values) + { + if (null != mediaRow.Cabinet) + { + filesByCabinetMedia.Add(mediaRow, new List()); + } + } + } + + foreach (FileFacade facade in fileFacades) + { + if (OutputType.Module == this.Output.Type) + { + filesByCabinetMedia[mergeModuleMediaRow].Add(facade); + } + else + { + MediaRow mediaRow; + if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow)) + { + Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId)); + continue; + } + + // When building a product, if the current file is not to be compressed or if + // the package set not to be compressed, don't cab it. + if (OutputType.Product == this.Output.Type && + (YesNoType.No == facade.File.Compressed || + (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) + { + uncompressedFiles.Add(facade); + } + else // file is marked compressed. + { + List cabinetFiles; + if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles)) + { + cabinetFiles.Add(facade); + } + else + { + Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId)); + } + } + } + } + } + + /// + /// Adds a row to the media table with cab name template filled in. + /// + /// + /// + /// + private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex) + { + MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); + currentMediaRow.DiskId = cabIndex; + currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex); + + Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]); + WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); + row.DiskId = cabIndex; + row.CompressionLevel = mediaTemplateRow.CompressionLevel; + + return currentMediaRow; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..2e2c5417 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -0,0 +1,1282 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Bind; + using WixToolset.Core.Bind; + using WixToolset.Core.WindowsInstaller.Databases; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + using WixToolset.Msi; + + /// + /// Binds a databse. + /// + internal class BindDatabaseCommand + { + // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. + private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); + + public BindDatabaseCommand(IBindContext context, Validator validator) + { + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + + this.BindPaths = context.BindPaths; + this.CabbingThreadCount = context.CabbingThreadCount; + this.CabCachePath = context.CabCachePath; + this.Codepage = context.Codepage; + this.DefaultCompressionLevel = context.DefaultCompressionLevel; + this.DelayedFields = context.DelayedFields; + this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + this.Extensions = context.Extensions; + this.Output = context.IntermediateRepresentation; + this.OutputPath = context.OutputPath; + this.PdbFile = context.OutputPdbPath; + this.IntermediateFolder = context.IntermediateFolder; + this.Validator = validator; + this.WixVariableResolver = context.WixVariableResolver; + + this.BackendExtensions = context.ExtensionManager.Create(); + } + + private IEnumerable BindPaths { get; } + + private int Codepage { get; } + + private int CabbingThreadCount { get; } + + private string CabCachePath { get; } + + private CompressionLevel DefaultCompressionLevel { get; } + + public IEnumerable DelayedFields { get; } + + public IEnumerable ExpectedEmbeddedFiles { get; } + + public bool DeltaBinaryPatch { get; set; } + + private IEnumerable BackendExtensions { get; } + + private IEnumerable Extensions { get; } + + private IEnumerable InspectorExtensions { get; } + + private string PdbFile { get; } + + private Output Output { get; } + + private string OutputPath { get; } + + private bool SuppressAddingValidationRows { get; } + + private bool SuppressLayout { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string IntermediateFolder { get; } + + private Validator Validator { get; } + + private IBindVariableResolver WixVariableResolver { get; } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable ContentFilePaths { get; private set; } + + public void Execute() + { + List fileTransfers = new List(); + + HashSet suppressedTableNames = new HashSet(); + + // If there are any fields to resolve later, create the cache to populate during bind. + IDictionary variableCache = null; + if (this.DelayedFields.Any()) + { + variableCache = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + } + + this.LocalizeUI(this.Output.Tables); + + // Process the summary information table before the other tables. + bool compressed; + bool longNames; + int installerVersion; + string modularizationGuid; + { + BindSummaryInfoCommand command = new BindSummaryInfoCommand(); + command.Output = this.Output; + command.Execute(); + + compressed = command.Compressed; + longNames = command.LongNames; + installerVersion = command.InstallerVersion; + modularizationGuid = command.ModularizationGuid; + } + + // Stop processing if an error previously occurred. + if (Messaging.Instance.EncounteredError) + { + return; + } + + // Modularize identifiers and add tables with real streams to the import tables. + if (OutputType.Module == this.Output.Type) + { + // Gather all the suppress modularization identifiers + HashSet suppressModularizationIdentifiers = null; + Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"]; + if (null != wixSuppressModularizationTable) + { + suppressModularizationIdentifiers = new HashSet(wixSuppressModularizationTable.Rows.Select(row => (string)row[0])); + } + + foreach (Table table in this.Output.Tables) + { + table.Modularize(modularizationGuid, suppressModularizationIdentifiers); + } + } + + // This must occur after all variables and source paths have been resolved and after modularization. + List fileFacades; + { + GetFileFacadesCommand command = new GetFileFacadesCommand(); + command.FileTable = this.Output.Tables["File"]; + command.WixFileTable = this.Output.Tables["WixFile"]; + command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"]; + command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"]; + command.Execute(); + + fileFacades = command.FileFacades; + } + + ////if (OutputType.Patch == this.Output.Type) + ////{ + //// foreach (SubStorage substorage in this.Output.SubStorages) + //// { + //// Output transform = substorage.Data; + + //// ResolveFieldsCommand command = new ResolveFieldsCommand(); + //// command.Tables = transform.Tables; + //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + //// command.FileManagerCore = this.FileManagerCore; + //// command.FileManagers = this.FileManagers; + //// command.SupportDelayedResolution = false; + //// command.TempFilesLocation = this.TempFilesLocation; + //// command.WixVariableResolver = this.WixVariableResolver; + //// command.Execute(); + + //// this.MergeUnrealTables(transform.Tables); + //// } + ////} + + { + CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand(); + command.PropertyTable = this.Output.Tables["Property"]; + command.WixPropertyTable = this.Output.Tables["WixProperty"]; + command.Execute(); + } + + if (Messaging.Instance.EncounteredError) + { + return; + } + + // Add binder variables for all properties. + Table propertyTable = this.Output.Tables["Property"]; + if (null != propertyTable) + { + foreach (PropertyRow propertyRow in propertyTable.Rows) + { + // Set the ProductCode if it is to be generated. + if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) + { + propertyRow.Value = Common.GenerateGuid(); + + // Update the target ProductCode in any instance transforms. + foreach (SubStorage subStorage in this.Output.SubStorages) + { + Output subStorageOutput = subStorage.Data; + if (OutputType.Transform != subStorageOutput.Type) + { + continue; + } + + Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; + foreach (Row row in instanceSummaryInformationTable.Rows) + { + if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) + { + row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); + break; + } + } + } + } + + // Add the property name and value to the variableCache. + if (null != variableCache) + { + string key = String.Concat("property.", Common.Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property)); + variableCache[key] = propertyRow.Value; + } + } + } + + // Extract files that come from cabinet files (this does not extract files from merge modules). + { + ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand(); + command.FilesWithEmbeddedFiles = this.ExpectedEmbeddedFiles; + command.Execute(); + } + + if (OutputType.Product == this.Output.Type) + { + // Retrieve files and their information from merge modules. + Table wixMergeTable = this.Output.Tables["WixMerge"]; + + if (null != wixMergeTable) + { + ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand(); + command.FileFacades = fileFacades; + command.FileTable = this.Output.Tables["File"]; + command.WixFileTable = this.Output.Tables["WixFile"]; + command.WixMergeTable = wixMergeTable; + command.OutputInstallerVersion = installerVersion; + command.SuppressLayout = this.SuppressLayout; + command.TempFilesLocation = this.IntermediateFolder; + command.Execute(); + + fileFacades.AddRange(command.MergeModulesFileFacades); + } + } + else if (OutputType.Patch == this.Output.Type) + { + // Merge transform data into the output object. + IEnumerable filesFromTransform = this.CopyFromTransformData(this.Output); + + fileFacades.AddRange(filesFromTransform); + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation()); + + // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table). + { + UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); + command.FileFacades = fileFacades; + command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); + command.ModularizationGuid = modularizationGuid; + command.Output = this.Output; + command.OverwriteHash = true; + command.TableDefinitions = this.TableDefinitions; + command.VariableCache = variableCache; + command.Execute(); + } + + // Set generated component guids. + this.SetComponentGuids(this.Output); + + // With the Component Guids set now we can create instance transforms. + this.CreateInstanceTransforms(this.Output); + + this.ValidateComponentGuids(this.Output); + + this.UpdateControlText(this.Output); + + if (this.DelayedFields.Any()) + { + ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand(); + command.OutputType = this.Output.Type; + command.DelayedFields = this.DelayedFields; + command.ModularizationGuid = null; + command.VariableCache = variableCache; + command.Execute(); + } + + // Assign files to media. + RowDictionary assignedMediaRows; + Dictionary> filesByCabinetMedia; + IEnumerable uncompressedFiles; + { + AssignMediaCommand command = new AssignMediaCommand(); + command.FilesCompressed = compressed; + command.FileFacades = fileFacades; + command.Output = this.Output; + command.TableDefinitions = this.TableDefinitions; + command.Execute(); + + assignedMediaRows = command.MediaRows; + filesByCabinetMedia = command.FileFacadesByCabinetMedia; + uncompressedFiles = command.UncompressedFileFacades; + } + + // Update file sequence. + this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows); + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + // Extended binder extensions can be called now that fields are resolved. + { + Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]); + + foreach (IBinderExtension extension in this.Extensions) + { + extension.AfterResolvedFields(this.Output); + } + + List updatedFileFacades = new List(); + + foreach (Row updatedFile in updatedFiles.Rows) + { + string updatedId = updatedFile.FieldAsString(0); + + FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId)); + + updatedFileFacades.Add(updatedFacade); + } + + if (updatedFileFacades.Any()) + { + UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); + command.FileFacades = fileFacades; + command.UpdateFileFacades = updatedFileFacades; + command.ModularizationGuid = modularizationGuid; + command.Output = this.Output; + command.OverwriteHash = true; + command.TableDefinitions = this.TableDefinitions; + command.VariableCache = variableCache; + command.Execute(); + } + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + Directory.CreateDirectory(this.IntermediateFolder); + + if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch) + { + CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand(); + command.FileFacades = fileFacades; + command.WixPatchIdTable = this.Output.Tables["WixPatchId"]; + command.TempFilesLocation = this.IntermediateFolder; + command.Execute(); + } + + // create cabinet files and process uncompressed files + string layoutDirectory = Path.GetDirectoryName(this.OutputPath); + if (!this.SuppressLayout || OutputType.Module == this.Output.Type) + { + Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles()); + + var command = new CreateCabinetsCommand(); + command.CabbingThreadCount = this.CabbingThreadCount; + command.CabCachePath = this.CabCachePath; + command.DefaultCompressionLevel = this.DefaultCompressionLevel; + command.Output = this.Output; + command.BackendExtensions = this.BackendExtensions; + command.LayoutDirectory = layoutDirectory; + command.Compressed = compressed; + command.FileRowsByCabinet = filesByCabinetMedia; + command.ResolveMedia = this.ResolveMedia; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.WixMediaTable = this.Output.Tables["WixMedia"]; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + } + + if (OutputType.Patch == this.Output.Type) + { + // copy output data back into the transforms + this.CopyToTransformData(this.Output); + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + // add back suppressed tables which must be present prior to merging in modules + if (OutputType.Product == this.Output.Type) + { + Table wixMergeTable = this.Output.Tables["WixMerge"]; + + if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) + { + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) + { + string sequenceTableName = sequence.ToString(); + Table sequenceTable = this.Output.Tables[sequenceTableName]; + + if (null == sequenceTable) + { + sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); + } + + if (0 == sequenceTable.Rows.Count) + { + suppressedTableNames.Add(sequenceTableName); + } + } + } + } + + //foreach (BinderExtension extension in this.Extensions) + //{ + // extension.PostBind(this.Context); + //} + + // generate database file + Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase()); + string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); + this.GenerateDatabase(this.Output, tempDatabaseFile, false, false); + + FileTransfer transfer; + if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future + { + transfer.Built = true; + fileTransfers.Add(transfer); + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + // Output the output to a file + Pdb pdb = new Pdb(); + pdb.Output = this.Output; + if (!String.IsNullOrEmpty(this.PdbFile)) + { + pdb.Save(this.PdbFile); + } + + // Merge modules. + if (OutputType.Product == this.Output.Type) + { + Messaging.Instance.OnMessage(WixVerboses.MergingModules()); + + MergeModulesCommand command = new MergeModulesCommand(); + command.FileFacades = fileFacades; + command.Output = this.Output; + command.OutputPath = tempDatabaseFile; + command.SuppressedTableNames = suppressedTableNames; + command.Execute(); + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + } + + // inspect the MSI prior to running ICEs + //InspectorCore inspectorCore = new InspectorCore(); + //foreach (InspectorExtension inspectorExtension in this.InspectorExtensions) + //{ + // inspectorExtension.Core = inspectorCore; + // inspectorExtension.InspectDatabase(tempDatabaseFile, pdb); + + // inspectorExtension.Core = null; // reset. + //} + + if (Messaging.Instance.EncounteredError) + { + return; + } + + // validate the output if there is an MSI validator + if (null != this.Validator) + { + Stopwatch stopwatch = Stopwatch.StartNew(); + + // set the output file for source line information + this.Validator.Output = this.Output; + + Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase()); + + this.Validator.Validate(tempDatabaseFile); + + stopwatch.Stop(); + Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + + // Stop processing if an error occurred. + if (Messaging.Instance.EncounteredError) + { + return; + } + } + + // Process uncompressed files. + if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) + { + var command = new ProcessUncompressedFilesCommand(); + command.Compressed = compressed; + command.FileFacades = uncompressedFiles; + command.LayoutDirectory = layoutDirectory; + command.LongNamesInImage = longNames; + command.MediaRows = assignedMediaRows; + command.ResolveMedia = this.ResolveMedia; + command.DatabasePath = tempDatabaseFile; + command.WixMediaTable = this.Output.Tables["WixMedia"]; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + } + + this.FileTransfers = fileTransfers; + this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList(); + } + + /// + /// Localize dialogs and controls. + /// + /// The tables to localize. + private void LocalizeUI(TableIndexedCollection tables) + { + Table dialogTable = tables["Dialog"]; + if (null != dialogTable) + { + foreach (Row row in dialogTable.Rows) + { + string dialog = (string)row[0]; + + if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + row[1] = localizedControl.X; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + row[2] = localizedControl.Y; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + row[3] = localizedControl.Width; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + row[4] = localizedControl.Height; + } + + row[5] = (int)row[5] | localizedControl.Attributes; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + row[6] = localizedControl.Text; + } + } + } + } + + Table controlTable = tables["Control"]; + if (null != controlTable) + { + foreach (Row row in controlTable.Rows) + { + string dialog = (string)row[0]; + string control = (string)row[1]; + + if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + row[3] = localizedControl.X.ToString(); + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + row[4] = localizedControl.Y.ToString(); + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + row[5] = localizedControl.Width.ToString(); + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + row[6] = localizedControl.Height.ToString(); + } + + row[7] = (int)row[7] | localizedControl.Attributes; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + row[9] = localizedControl.Text; + } + } + } + } + } + + /// + /// Copy file data between transform substorages and the patch output object + /// + /// The output to bind. + /// True if copying from transform to patch, false the other way. + private IEnumerable CopyFromTransformData(Output output) + { + var command = new CopyTransformDataCommand(); + command.CopyOutFileRows = true; + command.Output = output; + command.TableDefinitions = this.TableDefinitions; + command.Execute(); + + return command.FileFacades; + } + + /// + /// Copy file data between transform substorages and the patch output object + /// + /// The output to bind. + /// True if copying from transform to patch, false the other way. + private void CopyToTransformData(Output output) + { + var command = new CopyTransformDataCommand(); + command.CopyOutFileRows = false; + command.Output = output; + command.TableDefinitions = this.TableDefinitions; + command.Execute(); + } + + private void UpdateMediaSequences(OutputType outputType, IEnumerable fileFacades, RowDictionary mediaRows) + { + // Calculate sequence numbers and media disk id layout for all file media information objects. + if (OutputType.Module == outputType) + { + int lastSequence = 0; + foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. + { + facade.File.Sequence = ++lastSequence; + } + } + else + { + int lastSequence = 0; + MediaRow mediaRow = null; + Dictionary> patchGroups = new Dictionary>(); + + // sequence the non-patch-added files + foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. + { + if (null == mediaRow) + { + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + if (OutputType.Patch == outputType) + { + // patch Media cannot start at zero + lastSequence = mediaRow.LastSequence; + } + } + else if (mediaRow.DiskId != facade.WixFile.DiskId) + { + mediaRow.LastSequence = lastSequence; + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + } + + if (0 < facade.WixFile.PatchGroup) + { + List patchGroup = patchGroups[facade.WixFile.PatchGroup]; + + if (null == patchGroup) + { + patchGroup = new List(); + patchGroups.Add(facade.WixFile.PatchGroup, patchGroup); + } + + patchGroup.Add(facade); + } + else + { + facade.File.Sequence = ++lastSequence; + } + } + + if (null != mediaRow) + { + mediaRow.LastSequence = lastSequence; + mediaRow = null; + } + + // sequence the patch-added files + foreach (List patchGroup in patchGroups.Values) + { + foreach (FileFacade facade in patchGroup) + { + if (null == mediaRow) + { + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + } + else if (mediaRow.DiskId != facade.WixFile.DiskId) + { + mediaRow.LastSequence = lastSequence; + mediaRow = mediaRows.Get(facade.WixFile.DiskId); + } + + facade.File.Sequence = ++lastSequence; + } + } + + if (null != mediaRow) + { + mediaRow.LastSequence = lastSequence; + } + } + } + + /// + /// Set the guids for components with generatable guids. + /// + /// Internal representation of the database to operate on. + private void SetComponentGuids(Output output) + { + Table componentTable = output.Tables["Component"]; + if (null != componentTable) + { + Hashtable registryKeyRows = null; + Hashtable directories = null; + Hashtable componentIdGenSeeds = null; + Dictionary> fileRows = null; + + // find components with generatable guids + foreach (ComponentRow componentRow in componentTable.Rows) + { + // component guid will be generated + if ("*" == componentRow.Guid) + { + if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath) + { + Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers)); + } + else if (componentRow.IsRegistryKeyPath) + { + if (null == registryKeyRows) + { + Table registryTable = output.Tables["Registry"]; + + registryKeyRows = new Hashtable(registryTable.Rows.Count); + + foreach (Row registryRow in registryTable.Rows) + { + registryKeyRows.Add((string)registryRow[0], registryRow); + } + } + + Row foundRow = registryKeyRows[componentRow.KeyPath] as Row; + + string bitness = componentRow.Is64Bit ? "64" : String.Empty; + if (null != foundRow) + { + string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]); + componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant(); + } + } + else // must be a File KeyPath + { + // if the directory table hasn't been loaded into an indexed hash + // of directory ids to target names do that now. + if (null == directories) + { + Table directoryTable = output.Tables["Directory"]; + + int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0; + + directories = new Hashtable(numDirectoryTableRows); + + // get the target paths for all directories + if (null != directoryTable) + { + foreach (Row row in directoryTable.Rows) + { + // if the directory Id already exists, we will skip it here since + // checking for duplicate primary keys is done later when importing tables + // into database + if (directories.ContainsKey(row[0])) + { + continue; + } + + string targetName = Common.GetName((string)row[2], false, true); + directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName)); + } + } + } + + // if the component id generation seeds have not been indexed + // from the WixDirectory table do that now. + if (null == componentIdGenSeeds) + { + Table wixDirectoryTable = output.Tables["WixDirectory"]; + + int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0; + + componentIdGenSeeds = new Hashtable(numWixDirectoryRows); + + // if there are any WixDirectory rows, build up the Component Guid + // generation seeds indexed by Directory/@Id. + if (null != wixDirectoryTable) + { + foreach (Row row in wixDirectoryTable.Rows) + { + componentIdGenSeeds.Add(row[0], (string)row[1]); + } + } + } + + // if the file rows have not been indexed by File.Component yet + // then do that now + if (null == fileRows) + { + Table fileTable = output.Tables["File"]; + + int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0; + + fileRows = new Dictionary>(numFileRows); + + if (null != fileTable) + { + foreach (FileRow file in fileTable.Rows) + { + List files; + if (!fileRows.TryGetValue(file.Component, out files)) + { + files = new List(); + fileRows.Add(file.Component, files); + } + + files.Add(file); + } + } + } + + // validate component meets all the conditions to have a generated guid + List currentComponentFiles = fileRows[componentRow.Component]; + int numFilesInComponent = currentComponentFiles.Count; + string path = null; + + foreach (FileRow fileRow in currentComponentFiles) + { + if (fileRow.File == componentRow.KeyPath) + { + // calculate the key file's canonical target path + string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true); + string fileName = Common.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture); + path = Path.Combine(directoryPath, fileName); + + // find paths that are not canonicalized + if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || + path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || + path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || + path.StartsWith("TARGETDIR", StringComparison.Ordinal) || + path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || + path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) + { + Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path)); + } + + // if component has more than one file, the key path must be versioned + if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) + { + Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers)); + } + } + else + { + // not a key path, so it must be an unversioned file if component has more than one file + if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) + { + Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers)); + } + } + } + + // if the rules were followed, reward with a generated guid + if (!Messaging.Instance.EncounteredError) + { + componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant(); + } + } + } + } + } + } + + /// + /// Creates instance transform substorages in the output. + /// + /// Output containing instance transform definitions. + private void CreateInstanceTransforms(Output output) + { + // Create and add substorages for instance transforms. + Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"]; + if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count) + { + string targetProductCode = null; + string targetUpgradeCode = null; + string targetProductVersion = null; + + Table targetSummaryInformationTable = output.Tables["_SummaryInformation"]; + Table targetPropertyTable = output.Tables["Property"]; + + // Get the data from target database + foreach (Row propertyRow in targetPropertyTable.Rows) + { + if ("ProductCode" == (string)propertyRow[0]) + { + targetProductCode = (string)propertyRow[1]; + } + else if ("ProductVersion" == (string)propertyRow[0]) + { + targetProductVersion = (string)propertyRow[1]; + } + else if ("UpgradeCode" == (string)propertyRow[0]) + { + targetUpgradeCode = (string)propertyRow[1]; + } + } + + // Index the Instance Component Rows. + Dictionary instanceComponentGuids = new Dictionary(); + Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"]; + if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count) + { + foreach (Row row in targetInstanceComponentTable.Rows) + { + // Build up all the instances, we'll get the Components rows from the real Component table. + instanceComponentGuids.Add((string)row[0], null); + } + + Table targetComponentTable = output.Tables["Component"]; + foreach (ComponentRow componentRow in targetComponentTable.Rows) + { + string component = (string)componentRow[0]; + if (instanceComponentGuids.ContainsKey(component)) + { + instanceComponentGuids[component] = componentRow; + } + } + } + + // Generate the instance transforms + foreach (Row instanceRow in wixInstanceTransformsTable.Rows) + { + string instanceId = (string)instanceRow[0]; + + Output instanceTransform = new Output(instanceRow.SourceLineNumbers); + instanceTransform.Type = OutputType.Transform; + instanceTransform.Codepage = output.Codepage; + + Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + string targetPlatformAndLanguage = null; + + foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows) + { + if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE + { + targetPlatformAndLanguage = (string)summaryInformationRow[1]; + } + + // Copy the row's data to the transform. + Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null); + copyOfSummaryRow[0] = summaryInformationRow[0]; + copyOfSummaryRow[1] = summaryInformationRow[1]; + } + + // Modify the appropriate properties. + Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); + + // Change the ProductCode property + string productCode = (string)instanceRow[2]; + if ("*" == productCode) + { + productCode = Common.GenerateGuid(); + } + + Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + productCodeRow.Operation = RowOperation.Modify; + productCodeRow.Fields[1].Modified = true; + productCodeRow[0] = "ProductCode"; + productCodeRow[1] = productCode; + + // Change the instance property + Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + instanceIdRow.Operation = RowOperation.Modify; + instanceIdRow.Fields[1].Modified = true; + instanceIdRow[0] = (string)instanceRow[1]; + instanceIdRow[1] = instanceId; + + if (null != instanceRow[3]) + { + // Change the ProductName property + Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + productNameRow.Operation = RowOperation.Modify; + productNameRow.Fields[1].Modified = true; + productNameRow[0] = "ProductName"; + productNameRow[1] = (string)instanceRow[3]; + } + + if (null != instanceRow[4]) + { + // Change the UpgradeCode property + Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); + upgradeCodeRow.Operation = RowOperation.Modify; + upgradeCodeRow.Fields[1].Modified = true; + upgradeCodeRow[0] = "UpgradeCode"; + upgradeCodeRow[1] = instanceRow[4]; + + // Change the Upgrade table + Table targetUpgradeTable = output.Tables["Upgrade"]; + if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) + { + string upgradeId = (string)instanceRow[4]; + Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); + foreach (Row row in targetUpgradeTable.Rows) + { + // In case they are upgrading other codes to this new product, leave the ones that don't match the + // Product.UpgradeCode intact. + if (targetUpgradeCode == (string)row[0]) + { + Row upgradeRow = upgradeTable.CreateRow(null); + upgradeRow.Operation = RowOperation.Add; + upgradeRow.Fields[0].Modified = true; + // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. + // upgradeRow.Fields[0].PreviousData = (string)row[0]; + + // Inserting a new Upgrade record with the updated UpgradeCode + upgradeRow[0] = upgradeId; + upgradeRow[1] = row[1]; + upgradeRow[2] = row[2]; + upgradeRow[3] = row[3]; + upgradeRow[4] = row[4]; + upgradeRow[5] = row[5]; + upgradeRow[6] = row[6]; + + // Delete the old row + Row upgradeRemoveRow = upgradeTable.CreateRow(null); + upgradeRemoveRow.Operation = RowOperation.Delete; + upgradeRemoveRow[0] = row[0]; + upgradeRemoveRow[1] = row[1]; + upgradeRemoveRow[2] = row[2]; + upgradeRemoveRow[3] = row[3]; + upgradeRemoveRow[4] = row[4]; + upgradeRemoveRow[5] = row[5]; + upgradeRemoveRow[6] = row[6]; + } + } + } + } + + // If there are instance Components generate new GUIDs for them. + if (0 < instanceComponentGuids.Count) + { + Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); + foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values) + { + string guid = targetComponentRow.Guid; + if (!String.IsNullOrEmpty(guid)) + { + Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); + instanceComponentRow.Operation = RowOperation.Modify; + instanceComponentRow.Fields[1].Modified = true; + instanceComponentRow[0] = targetComponentRow[0]; + instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture); + instanceComponentRow[2] = targetComponentRow[2]; + instanceComponentRow[3] = targetComponentRow[3]; + instanceComponentRow[4] = targetComponentRow[4]; + instanceComponentRow[5] = targetComponentRow[5]; + } + } + } + + // Update the summary information + Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count); + foreach (Row row in instanceSummaryInformationTable.Rows) + { + summaryRows[row[0]] = row; + + if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) + { + row[1] = targetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) + { + row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); + } + else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) + { + row[1] = 0; + } + else if ((int)SummaryInformation.Transform.Security == (int)row[0]) + { + row[1] = "4"; + } + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + Row summaryRow = instanceSummaryInformationTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = targetPlatformAndLanguage; + } + else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) + { + Row summaryRow = instanceSummaryInformationTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = "0"; + } + else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) + { + Row summaryRow = instanceSummaryInformationTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + + output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); + } + } + } + + /// + /// Validate that there are no duplicate GUIDs in the output. + /// + /// + /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a + /// warning, as the conditions might be mutually exclusive. + /// + private void ValidateComponentGuids(Output output) + { + Table componentTable = output.Tables["Component"]; + if (null != componentTable) + { + Dictionary componentGuidConditions = new Dictionary(componentTable.Rows.Count); + + foreach (ComponentRow row in componentTable.Rows) + { + // we don't care about unmanaged components and if there's a * GUID remaining, + // there's already an error that prevented it from being replaced with a real GUID. + if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid) + { + bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); + bool allComponentsHaveConditions = thisComponentHasCondition; + + if (componentGuidConditions.ContainsKey(row.Guid)) + { + allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition; + + if (allComponentsHaveConditions) + { + Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid)); + } + else + { + Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid)); + } + } + + componentGuidConditions[row.Guid] = allComponentsHaveConditions; + } + } + } + } + + /// + /// Update Control and BBControl text by reading from files when necessary. + /// + /// Internal representation of the msi database to operate upon. + private void UpdateControlText(Output output) + { + UpdateControlTextCommand command = new UpdateControlTextCommand(); + command.BBControlTable = output.Tables["BBControl"]; + command.WixBBControlTable = output.Tables["WixBBControl"]; + command.ControlTable = output.Tables["Control"]; + command.WixControlTable = output.Tables["WixControl"]; + command.Execute(); + } + + private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory) + { + string layout = null; + + foreach (var extension in this.BackendExtensions) + { + layout = extension.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory); + if (!String.IsNullOrEmpty(layout)) + { + break; + } + } + + // If no binder file manager resolved the layout, do the default behavior. + if (String.IsNullOrEmpty(layout)) + { + if (String.IsNullOrEmpty(mediaLayoutDirectory)) + { + layout = layoutDirectory; + } + else if (Path.IsPathRooted(mediaLayoutDirectory)) + { + layout = mediaLayoutDirectory; + } + else + { + layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); + } + } + + return layout; + } + + /// + /// Creates the MSI/MSM/PCP database. + /// + /// Output to create database for. + /// The database file to create. + /// Whether to keep columns added in a transform. + /// Whether to use a subdirectory based on the file name for intermediate files. + private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) + { + var command = new GenerateDatabaseCommand(); + command.Extensions = this.Extensions; + command.Output = output; + command.OutputPath = databaseFile; + command.KeepAddedColumns = keepAddedColumns; + command.UseSubDirectory = useSubdirectory; + command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.Codepage = this.Codepage; + command.Execute(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs new file mode 100644 index 00000000..5471792d --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs @@ -0,0 +1,135 @@ +// 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.Databases +{ + using System; + using System.Globalization; + using WixToolset.Data; + + /// + /// Binds the summary information table of a database. + /// + internal class BindSummaryInfoCommand + { + /// + /// The output to bind. + /// + public Output Output { private get; set; } + + /// + /// Returns a flag indicating if files are compressed by default. + /// + public bool Compressed { get; private set; } + + /// + /// Returns a flag indicating if uncompressed files use long filenames. + /// + public bool LongNames { get; private set; } + + public int InstallerVersion { get; private set; } + + /// + /// Modularization guid, or null if the output is not a module. + /// + public string ModularizationGuid { get; private set; } + + public void Execute() + { + this.Compressed = false; + this.LongNames = false; + this.InstallerVersion = 0; + this.ModularizationGuid = null; + + Table summaryInformationTable = this.Output.Tables["_SummaryInformation"]; + + if (null != summaryInformationTable) + { + bool foundCreateDataTime = false; + bool foundLastSaveDataTime = false; + bool foundCreatingApplication = false; + string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); + + foreach (Row summaryInformationRow in summaryInformationTable.Rows) + { + switch (summaryInformationRow.FieldAsInteger(0)) + { + case 1: // PID_CODEPAGE + // make sure the code page is an int and not a web name or null + string codepage = summaryInformationRow.FieldAsString(1); + + if (null == codepage) + { + codepage = "0"; + } + else + { + summaryInformationRow[1] = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture); + } + break; + case 9: // PID_REVNUMBER + string packageCode = (string)summaryInformationRow[1]; + + if (OutputType.Module == this.Output.Type) + { + this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_'); + } + else if ("*" == packageCode) + { + // set the revision number (package/patch code) if it should be automatically generated + summaryInformationRow[1] = Common.GenerateGuid(); + } + break; + case 12: // PID_CREATE_DTM + foundCreateDataTime = true; + break; + case 13: // PID_LASTSAVE_DTM + foundLastSaveDataTime = true; + break; + case 14: + this.InstallerVersion = summaryInformationRow.FieldAsInteger(1); + break; + case 15: // PID_WORDCOUNT + if (OutputType.Patch == this.Output.Type) + { + this.LongNames = true; + this.Compressed = true; + } + else + { + this.LongNames = (0 == (summaryInformationRow.FieldAsInteger(1) & 1)); + this.Compressed = (2 == (summaryInformationRow.FieldAsInteger(1) & 2)); + } + break; + case 18: // PID_APPNAME + foundCreatingApplication = true; + break; + } + } + + // add a summary information row for the create time/date property if its not already set + if (!foundCreateDataTime) + { + Row createTimeDateRow = summaryInformationTable.CreateRow(null); + createTimeDateRow[0] = 12; + createTimeDateRow[1] = now; + } + + // add a summary information row for the last save time/date property if its not already set + if (!foundLastSaveDataTime) + { + Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null); + lastSaveTimeDateRow[0] = 13; + lastSaveTimeDateRow[1] = now; + } + + // add a summary information row for the creating application property if its not already set + if (!foundCreatingApplication) + { + Row creatingApplicationRow = summaryInformationTable.CreateRow(null); + creatingApplicationRow[0] = 18; + creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString()); + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..425d1f9c --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -0,0 +1,470 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Msi; + using WixToolset.Core.Native; + + internal class BindTransformCommand + { + public IEnumerable Extensions { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public string TempFilesLocation { private get; set; } + + public Output Transform { private get; set; } + + public string OutputPath { private get; set; } + + public void Execute() + { + int transformFlags = 0; + + Output targetOutput = new Output(null); + Output updatedOutput = new Output(null); + + // TODO: handle added columns + + // to generate a localized transform, both the target and updated + // databases need to have the same code page. the only reason to + // set different code pages is to support localized primary key + // columns, but that would only support deleting rows. if this + // becomes necessary, define a PreviousCodepage property on the + // Output class and persist this throughout transform generation. + targetOutput.Codepage = this.Transform.Codepage; + updatedOutput.Codepage = this.Transform.Codepage; + + // remove certain Property rows which will be populated from summary information values + string targetUpgradeCode = null; + string updatedUpgradeCode = null; + + Table propertyTable = this.Transform.Tables["Property"]; + if (null != propertyTable) + { + for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) + { + Row row = propertyTable.Rows[i]; + + if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) + { + propertyTable.Rows.RemoveAt(i); + + if ("UpgradeCode" == (string)row[0]) + { + updatedUpgradeCode = (string)row[1]; + } + } + } + } + + Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); + Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); + + // process special summary information values + foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows) + { + if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) + { + // convert from a web name if provided + string codePage = (string)row.Fields[1].Data; + if (null == codePage) + { + codePage = "0"; + } + else + { + codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); + } + + string previousCodePage = (string)row.Fields[1].PreviousData; + if (null == previousCodePage) + { + previousCodePage = "0"; + } + else + { + previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); + } + + Row targetCodePageRow = targetSummaryInfo.CreateRow(null); + targetCodePageRow[0] = 1; // PID_CODEPAGE + targetCodePageRow[1] = previousCodePage; + + Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); + updatedCodePageRow[0] = 1; // PID_CODEPAGE + updatedCodePageRow[1] = codePage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || + (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) + { + // the target language + string[] propertyData = ((string)row[1]).Split(';'); + string lang = 2 == propertyData.Length ? propertyData[1] : "0"; + + Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; + Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; + + Row productLanguageRow = tempPropertyTable.CreateRow(null); + productLanguageRow[0] = "ProductLanguage"; + productLanguageRow[1] = lang; + + // set the platform;language on the MSI to be generated + Row templateRow = tempSummaryInfo.CreateRow(null); + templateRow[0] = 7; // PID_TEMPLATE + templateRow[1] = (string)row[1]; + } + else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) + { + string[] propertyData = ((string)row[1]).Split(';'); + + Row targetProductCodeRow = targetPropertyTable.CreateRow(null); + targetProductCodeRow[0] = "ProductCode"; + targetProductCodeRow[1] = propertyData[0].Substring(0, 38); + + Row targetProductVersionRow = targetPropertyTable.CreateRow(null); + targetProductVersionRow[0] = "ProductVersion"; + targetProductVersionRow[1] = propertyData[0].Substring(38); + + Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); + updatedProductCodeRow[0] = "ProductCode"; + updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); + + Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); + updatedProductVersionRow[0] = "ProductVersion"; + updatedProductVersionRow[1] = propertyData[1].Substring(38); + + // UpgradeCode is optional and may not exists in the target + // or upgraded databases, so do not include a null-valued + // UpgradeCode property. + + targetUpgradeCode = propertyData[2]; + if (!String.IsNullOrEmpty(targetUpgradeCode)) + { + Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); + targetUpgradeCodeRow[0] = "UpgradeCode"; + targetUpgradeCodeRow[1] = targetUpgradeCode; + + // If the target UpgradeCode is specified, an updated + // UpgradeCode is required. + if (String.IsNullOrEmpty(updatedUpgradeCode)) + { + updatedUpgradeCode = targetUpgradeCode; + } + } + + if (!String.IsNullOrEmpty(updatedUpgradeCode)) + { + Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); + updatedUpgradeCodeRow[0] = "UpgradeCode"; + updatedUpgradeCodeRow[1] = updatedUpgradeCode; + } + } + else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) + { + transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) + { + // PID_LASTPRINTED should be null for transforms + row.Operation = RowOperation.None; + } + else + { + // add everything else as is + Row targetRow = targetSummaryInfo.CreateRow(null); + targetRow[0] = row[0]; + targetRow[1] = row[1]; + + Row updatedRow = updatedSummaryInfo.CreateRow(null); + updatedRow[0] = row[0]; + updatedRow[1] = row[1]; + } + } + + // Validate that both databases have an UpgradeCode if the + // authoring transform will validate the UpgradeCode; otherwise, + // MsiCreateTransformSummaryinfo() will fail with 1620. + if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && + (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) + { + Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired()); + } + + string emptyFile = null; + + foreach (Table table in this.Transform.Tables) + { + // Ignore unreal tables when building transforms except the _Stream table. + // These tables are ignored when generating the database so there is no reason + // to process them here. + if (table.Definition.Unreal && "_Streams" != table.Name) + { + continue; + } + + // process table operations + switch (table.Operation) + { + case TableOperation.Add: + updatedOutput.EnsureTable(table.Definition); + break; + case TableOperation.Drop: + targetOutput.EnsureTable(table.Definition); + continue; + default: + targetOutput.EnsureTable(table.Definition); + updatedOutput.EnsureTable(table.Definition); + break; + } + + // process row operations + foreach (Row row in table.Rows) + { + switch (row.Operation) + { + case RowOperation.Add: + Table updatedTable = updatedOutput.EnsureTable(table.Definition); + updatedTable.Rows.Add(row); + continue; + case RowOperation.Delete: + Table targetTable = targetOutput.EnsureTable(table.Definition); + targetTable.Rows.Add(row); + + // fill-in non-primary key values + foreach (Field field in row.Fields) + { + if (!field.Column.PrimaryKey) + { + if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) + { + field.Data = field.Column.MinValue; + } + else if (ColumnType.Object == field.Column.Type) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.TempFilesLocation, "empty"); + } + + field.Data = emptyFile; + } + else + { + field.Data = "0"; + } + } + } + continue; + } + + // Assure that the file table's sequence is populated + if ("File" == table.Name) + { + foreach (Row fileRow in table.Rows) + { + if (null == fileRow[7]) + { + if (RowOperation.Add == fileRow.Operation) + { + Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); + break; + } + + // Set to 1 to prevent invalid IDT file from being generated + fileRow[7] = 1; + } + } + } + + // process modified and unmodified rows + bool modifiedRow = false; + Row targetRow = new Row(null, table.Definition); + Row updatedRow = row; + for (int i = 0; i < row.Fields.Length; i++) + { + Field updatedField = row.Fields[i]; + + if (updatedField.Modified) + { + // set a different value in the target row to ensure this value will be modified during transform generation + if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) + { + if (null == updatedField.Data || 1 != (int)updatedField.Data) + { + targetRow[i] = 1; + } + else + { + targetRow[i] = 2; + } + } + else if (ColumnType.Object == updatedField.Column.Type) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.TempFilesLocation, "empty"); + } + + targetRow[i] = emptyFile; + } + else + { + if ("0" != (string)updatedField.Data) + { + targetRow[i] = "0"; + } + else + { + targetRow[i] = "1"; + } + } + + modifiedRow = true; + } + else if (ColumnType.Object == updatedField.Column.Type) + { + ObjectField objectField = (ObjectField)updatedField; + + // create an empty file for comparing against + if (null == objectField.PreviousData) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.TempFilesLocation, "empty"); + } + + targetRow[i] = emptyFile; + modifiedRow = true; + } + else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data)) + { + targetRow[i] = objectField.PreviousData; + modifiedRow = true; + } + } + else // unmodified + { + if (null != updatedField.Data) + { + targetRow[i] = updatedField.Data; + } + } + } + + // modified rows and certain special rows go in the target and updated msi databases + if (modifiedRow || + ("Property" == table.Name && + ("ProductCode" == (string)row[0] || + "ProductLanguage" == (string)row[0] || + "ProductVersion" == (string)row[0] || + "UpgradeCode" == (string)row[0]))) + { + Table targetTable = targetOutput.EnsureTable(table.Definition); + targetTable.Rows.Add(targetRow); + + Table updatedTable = updatedOutput.EnsureTable(table.Definition); + updatedTable.Rows.Add(updatedRow); + } + } + } + + //foreach (BinderExtension extension in this.Extensions) + //{ + // extension.PostBind(this.Context); + //} + + // Any errors encountered up to this point can cause errors during generation. + if (Messaging.Instance.EncounteredError) + { + return; + } + + string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); + string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); + string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); + + try + { + if (!String.IsNullOrEmpty(emptyFile)) + { + using (FileStream fileStream = File.Create(emptyFile)) + { + } + } + + this.GenerateDatabase(targetOutput, targetDatabaseFile, false); + this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true); + + // make sure the directory exists + Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); + + // create the transform file + using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) + { + using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) + { + if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) + { + updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); + } + else + { + Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); + } + } + } + } + finally + { + if (!String.IsNullOrEmpty(emptyFile)) + { + File.Delete(emptyFile); + } + } + } + + private bool CompareFiles(string targetFile, string updatedFile) + { + bool? compared = null; + foreach (var extension in this.Extensions) + { + compared = extension.CompareFiles(targetFile, updatedFile); + if (compared.HasValue) + { + break; + } + } + + if (!compared.HasValue) + { + throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. + } + + return compared.Value; + } + + private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns) + { + var command = new GenerateDatabaseCommand(); + command.Codepage = output.Codepage; + command.Extensions = this.Extensions; + command.KeepAddedColumns = keepAddedColumns; + command.Output = output; + command.OutputPath = outputPath; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.TempFilesLocation; + command.SuppressAddingValidationRows = true; + command.UseSubDirectory = true; + command.Execute(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs new file mode 100644 index 00000000..b2cc76fc --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs @@ -0,0 +1,177 @@ +// 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.Databases +{ + using System; + using System.Collections; + using System.IO; + using System.Linq; + using System.Threading; + using WixToolset.Core.Bind; + using WixToolset.Core.Cab; + using WixToolset.Data; + + /// + /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple + /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished. + /// + internal sealed class CabinetBuilder + { + private Queue cabinetWorkItems; + private object lockObject; + private int threadCount; + + // Address of Binder's callback function for Cabinet Splitting + private IntPtr newCabNamesCallBackAddress; + + public int MaximumCabinetSizeForLargeFileSplitting { get; set; } + + public int MaximumUncompressedMediaSize { get; set; } + + /// + /// Instantiate a new CabinetBuilder. + /// + /// number of threads to use + /// Address of Binder's callback function for Cabinet Splitting + public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress) + { + if (0 >= threadCount) + { + throw new ArgumentOutOfRangeException("threadCount"); + } + + this.cabinetWorkItems = new Queue(); + this.lockObject = new object(); + + this.threadCount = threadCount; + + // Set Address of Binder's callback function for Cabinet Splitting + this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; + } + + /// + /// Enqueues a CabinetWorkItem to the queue. + /// + /// cabinet work item + public void Enqueue(CabinetWorkItem cabinetWorkItem) + { + this.cabinetWorkItems.Enqueue(cabinetWorkItem); + } + + /// + /// Create the queued cabinets. + /// + /// error message number (zero if no error) + public void CreateQueuedCabinets() + { + // don't create more threads than the number of cabinets to build + if (this.cabinetWorkItems.Count < this.threadCount) + { + this.threadCount = this.cabinetWorkItems.Count; + } + + if (0 < this.threadCount) + { + Thread[] threads = new Thread[this.threadCount]; + + for (int i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); + threads[i].Start(); + } + + // wait for all threads to finish + foreach (Thread thread in threads) + { + thread.Join(); + } + } + } + + /// + /// This function gets called by multiple threads to do actual work. + /// It takes one work item at a time and calls this.CreateCabinet(). + /// It does not return until cabinetWorkItems queue is empty + /// + private void ProcessWorkItems() + { + try + { + while (true) + { + CabinetWorkItem cabinetWorkItem; + + lock (this.cabinetWorkItems) + { + // check if there are any more cabinets to create + if (0 == this.cabinetWorkItems.Count) + { + break; + } + + cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue(); + } + + // create a cabinet + this.CreateCabinet(cabinetWorkItem); + } + } + catch (WixException we) + { + Messaging.Instance.OnMessage(we.Error); + } + catch (Exception e) + { + Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace)); + } + } + + /// + /// Creates a cabinet using the wixcab.dll interop layer. + /// + /// CabinetWorkItem containing information about the cabinet to create. + private void CreateCabinet(CabinetWorkItem cabinetWorkItem) + { + Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile)); + + int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting + ulong maxPreCompressedSizeInBytes = 0; + + if (MaximumCabinetSizeForLargeFileSplitting != 0) + { + // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize + // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file + if (1 == cabinetWorkItem.FileFacades.Count()) + { + // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs + // Get the Value for Max Uncompressed Media Size + maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024; + + foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row + { + if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes) + { + // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting + maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting; + } + } + } + } + + // create the cabinet file + string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile); + string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile); + + using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel)) + { + foreach (FileFacade facade in cabinetWorkItem.FileFacades) + { + cab.AddFile(facade); + } + + cab.Complete(newCabNamesCallBackAddress); + } + } + } +} + diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs new file mode 100644 index 00000000..df1ccecf --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs @@ -0,0 +1,122 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.Cab; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Extensibility; + + public class CabinetResolver + { + public CabinetResolver(string cabCachePath, IEnumerable backendExtensions) + { + this.CabCachePath = cabCachePath; + + this.BackendExtensions = backendExtensions; + } + + private string CabCachePath { get; } + + private IEnumerable BackendExtensions { get; } + + public ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) + { + var filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); + + ResolvedCabinet resolved = null; + + foreach (var extension in this.BackendExtensions) + { + resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); + + if (null != resolved) + { + return resolved; + } + } + + // By default cabinet should be built and moved to the suggested location. + resolved = new ResolvedCabinet() { BuildOption = CabinetBuildOption.BuildAndMove, Path = cabinetPath }; + + // If a cabinet cache path was provided, change the location for the cabinet + // to be built to and check if there is a cabinet that can be reused. + if (!String.IsNullOrEmpty(this.CabCachePath)) + { + string cabinetName = Path.GetFileName(cabinetPath); + resolved.Path = Path.Combine(this.CabCachePath, cabinetName); + + if (CheckFileExists(resolved.Path)) + { + // Assume that none of the following are true: + // 1. any files are added or removed + // 2. order of files changed or names changed + // 3. modified time changed + bool cabinetValid = true; + + // Need to force garbage collection of WixEnumerateCab to ensure the handle + // associated with it is closed before it is reused. + using (var wixEnumerateCab = new WixEnumerateCab()) + { + List fileList = wixEnumerateCab.Enumerate(resolved.Path); + + if (filesWithPath.Count() != fileList.Count) + { + cabinetValid = false; + } + else + { + int i = 0; + foreach (BindFileWithPath file in filesWithPath) + { + // First check that the file identifiers match because that is quick and easy. + CabinetFileInfo cabFileInfo = fileList[i]; + cabinetValid = (cabFileInfo.FileId == file.Id); + if (cabinetValid) + { + // Still valid so ensure the file sizes are the same. + FileInfo fileInfo = new FileInfo(file.Path); + cabinetValid = (cabFileInfo.Size == fileInfo.Length); + if (cabinetValid) + { + // Still valid so ensure the source time stamp hasn't changed. Thus we need + // to convert the source file time stamp into a cabinet compatible data/time. + Native.CabInterop.DateTimeToCabDateAndTime(fileInfo.LastWriteTime, out var sourceCabDate, out var sourceCabTime); + cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime); + } + } + + if (!cabinetValid) + { + break; + } + + i++; + } + } + } + + resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; + } + } + + return resolved; + } + + private static bool CheckFileExists(string path) + { + try + { + return File.Exists(path); + } + catch (ArgumentException) + { + throw new WixException(WixErrors.IllegalCharactersInPath(path)); + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs new file mode 100644 index 00000000..dcafcd36 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs @@ -0,0 +1,79 @@ +// 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.Databases +{ + using System.Collections.Generic; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Rows; + + /// + /// A cabinet builder work item. + /// + internal sealed class CabinetWorkItem + { + private string cabinetFile; + private CompressionLevel compressionLevel; + //private BinderFileManager binderFileManager; + private int maxThreshold; + + /// + /// Instantiate a new CabinetWorkItem. + /// + /// The collection of files in this cabinet. + /// The cabinet file. + /// Maximum threshold for each cabinet. + /// The compression level of the cabinet. + /// The binder file manager. + public CabinetWorkItem(IEnumerable fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/) + { + this.cabinetFile = cabinetFile; + this.compressionLevel = compressionLevel; + this.FileFacades = fileFacades; + //this.binderFileManager = binderFileManager; + this.maxThreshold = maxThreshold; + } + + /// + /// Gets the cabinet file. + /// + /// The cabinet file. + public string CabinetFile + { + get { return this.cabinetFile; } + } + + /// + /// Gets the compression level of the cabinet. + /// + /// The compression level of the cabinet. + public CompressionLevel CompressionLevel + { + get { return this.compressionLevel; } + } + + /// + /// Gets the collection of files in this cabinet. + /// + /// The collection of files in this cabinet. + public IEnumerable FileFacades { get; private set; } + + /// + /// Gets the binder file manager. + /// + /// The binder file manager. + //public BinderFileManager BinderFileManager + //{ + // get { return this.binderFileManager; } + //} + + /// + /// Gets the max threshold. + /// + /// The maximum threshold for a folder in a cabinet. + public int MaxThreshold + { + get { return this.maxThreshold; } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs new file mode 100644 index 00000000..d4d3799f --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs @@ -0,0 +1,91 @@ +// 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.Databases +{ + using System; + using System.Collections; + using System.Globalization; + using WixToolset.MergeMod; + + /// + /// Callback object for configurable merge modules. + /// + internal sealed class ConfigurationCallback : IMsmConfigureModule + { + private const int SOk = 0x0; + private const int SFalse = 0x1; + private Hashtable configurationData; + + /// + /// Creates a ConfigurationCallback object. + /// + /// String to break up into name/value pairs. + public ConfigurationCallback(string configData) + { + if (String.IsNullOrEmpty(configData)) + { + throw new ArgumentNullException("configData"); + } + + string[] pairs = configData.Split(','); + this.configurationData = new Hashtable(pairs.Length); + for (int i = 0; i < pairs.Length; ++i) + { + string[] nameVal = pairs[i].Split('='); + string name = nameVal[0]; + string value = nameVal[1]; + + name = name.Replace("%2C", ","); + name = name.Replace("%3D", "="); + name = name.Replace("%25", "%"); + + value = value.Replace("%2C", ","); + value = value.Replace("%3D", "="); + value = value.Replace("%25", "%"); + + this.configurationData[name] = value; + } + } + + /// + /// Returns text data based on name. + /// + /// Name of value to return. + /// Out param to put configuration data into. + /// S_OK if value provided, S_FALSE if not. + public int ProvideTextData(string name, out string configData) + { + if (this.configurationData.Contains(name)) + { + configData = (string)this.configurationData[name]; + return SOk; + } + else + { + configData = null; + return SFalse; + } + } + + /// + /// Returns integer data based on name. + /// + /// Name of value to return. + /// Out param to put configuration data into. + /// S_OK if value provided, S_FALSE if not. + public int ProvideIntegerData(string name, out int configData) + { + if (this.configurationData.Contains(name)) + { + string val = (string)this.configurationData[name]; + configData = Convert.ToInt32(val, CultureInfo.InvariantCulture); + return SOk; + } + else + { + configData = 0; + return SFalse; + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs new file mode 100644 index 00000000..6388a352 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs @@ -0,0 +1,606 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + using WixToolset.Core.Native; + using WixToolset.Core.Bind; + + internal class CopyTransformDataCommand + { + public bool CopyOutFileRows { private get; set; } + + public IEnumerable Extensions { private get; set; } + + public Output Output { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public IEnumerable FileFacades { get; private set; } + + public void Execute() + { + Debug.Assert(OutputType.Patch != this.Output.Type); + + List allFileRows = this.CopyOutFileRows ? new List() : null; + +#if false // TODO: Fix this patching related code to work correctly with FileFacades. + bool copyToPatch = (allFileRows != null); + bool copyFromPatch = !copyToPatch; + + RowDictionary patchMediaRows = new RowDictionary(); + + Dictionary> patchMediaFileRows = new Dictionary>(); + + Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); + Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); + + if (copyFromPatch) + { + // index patch files by diskId+fileId + foreach (WixFileRow patchFileRow in patchFileTable.Rows) + { + int diskId = patchFileRow.DiskId; + RowDictionary mediaFileRows; + if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) + { + mediaFileRows = new RowDictionary(); + patchMediaFileRows.Add(diskId, mediaFileRows); + } + + mediaFileRows.Add(patchFileRow); + } + + Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); + patchMediaRows = new RowDictionary(patchMediaTable); + } + + // index paired transforms + Dictionary pairedTransforms = new Dictionary(); + foreach (SubStorage substorage in this.Output.SubStorages) + { + if (substorage.Name.StartsWith("#")) + { + pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data); + } + } + + try + { + // copy File bind data into substorages + foreach (SubStorage substorage in this.Output.SubStorages) + { + if (substorage.Name.StartsWith("#")) + { + // no changes necessary for paired transforms + continue; + } + + Output mainTransform = substorage.Data; + Table mainWixFileTable = mainTransform.Tables["WixFile"]; + Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; + + this.FileManagerCore.ActiveSubStorage = substorage; + + RowDictionary mainWixFiles = new RowDictionary(mainWixFileTable); + RowDictionary mainMsiFileHashIndex = new RowDictionary(); + + Table mainFileTable = mainTransform.Tables["File"]; + Output pairedTransform = (Output)pairedTransforms[substorage.Name]; + + // copy Media.LastSequence and index the MsiFileHash table if it exists. + if (copyFromPatch) + { + Table pairedMediaTable = pairedTransform.Tables["Media"]; + foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) + { + MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); + pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; + } + + if (null != mainMsiFileHashTable) + { + mainMsiFileHashIndex = new RowDictionary(mainMsiFileHashTable); + } + + // Validate file row changes for keypath-related issues + this.ValidateFileRowChanges(mainTransform); + } + + // Index File table of pairedTransform + Table pairedFileTable = pairedTransform.Tables["File"]; + RowDictionary pairedFileRows = new RowDictionary(pairedFileTable); + + if (null != mainFileTable) + { + if (copyFromPatch) + { + // 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) + { + if (RowOperation.Delete == mainFileRow.Operation) + { + continue; + } + else if (RowOperation.None == mainFileRow.Operation && !copyToPatch) + { + continue; + } + + WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File); + + if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. + { + ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6]; + FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File); + + // If the file is new, we always need to add it to the patch. + if (mainFileRow.Operation != RowOperation.Add) + { + // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. + if (null == objectField.PreviousData) + { + if (mainFileRow.Operation == RowOperation.None) + { + continue; + } + } + else + { + // TODO: should this entire condition be placed in the binder file manager? + if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && + !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) + { + // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. + mainFileRow.Operation = RowOperation.Modify; + if (null != pairedFileRow) + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Modify; + } + } + else + { + // The File is same. We need mark all the attributes as unchanged. + mainFileRow.Operation = RowOperation.None; + foreach (Field field in mainFileRow.Fields) + { + field.Modified = false; + } + + if (null != pairedFileRow) + { + pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = false; + pairedFileRow.Operation = RowOperation.None; + } + continue; + } + } + } + else if (null != pairedFileRow) // RowOperation.Add + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Add; + } + } + + // index patch files by diskId+fileId + int diskId = mainWixFileRow.DiskId; + + RowDictionary mediaFileRows; + if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) + { + mediaFileRows = new RowDictionary(); + patchMediaFileRows.Add(diskId, mediaFileRows); + } + + string fileId = mainFileRow.File; + WixFileRow patchFileRow = mediaFileRows.Get(fileId); + if (copyToPatch) + { + if (null == patchFileRow) + { + FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + patchActualFileRow.CopyFrom(mainFileRow); + + patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + patchFileRow.CopyFrom(mainWixFileRow); + + mediaFileRows.Add(patchFileRow); + + allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right? + } + else + { + // TODO: confirm the rest of data is identical? + + // make sure Source is same. Otherwise we are silently ignoring a file. + if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) + { + Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); + } + + // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. + patchFileRow.AppendPreviousDataFrom(mainWixFileRow); + } + } + else + { + // copy data from the patch back to the transform + if (null != patchFileRow) + { + FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId); + for (int i = 0; i < patchFileRow.Fields.Length; i++) + { + string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString(); + string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString(); + + 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). + } + // File.Attributes should not changed for binary deltas + else if (6 == i) + { + if (null != patchFileRow.Patch) + { + // File.Attribute should not change for binary deltas + pairedFileRow.Attributes = mainFileRow.Attributes; + mainFileRow.Fields[i].Modified = false; + } + } + // File.Sequence is updated in pairedTransform, not mainTransform + else if (7 == i) + { + // file sequence is updated in Patch table instead of File table for delta patches + if (null != patchFileRow.Patch) + { + pairedFileRow.Fields[i].Modified = false; + } + else + { + 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; + } + } + } + + // copy MsiFileHash row for this File + Row patchHashRow; + if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow)) + { + patchHashRow = patchFileRow.Hash; + } + + if (null != patchHashRow) + { + Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); + Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); + for (int 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; + } + + // copy MsiAssemblyName rows for this File + List patchAssemblyNameRows = patchFileRow.AssemblyNames; + if (null != patchAssemblyNameRows) + { + Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + foreach (Row patchAssemblyNameRow in patchAssemblyNameRows) + { + // Copy if there isn't an identical modified/added row already in the transform. + bool foundMatchingModifiedRow = false; + foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows) + { + if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) + { + foundMatchingModifiedRow = true; + break; + } + } + + if (!foundMatchingModifiedRow) + { + Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); + for (int 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; + } + } + } + + // Add patch header for this file + if (null != patchFileRow.Patch) + { + // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. + AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); + AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); + + // Add to Patch table + Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); + if (0 == patchTable.Rows.Count) + { + patchTable.Operation = TableOperation.Add; + } + + Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); + patchRow[0] = patchFileRow.File; + patchRow[1] = patchFileRow.Sequence; + + FileInfo patchFile = new FileInfo(patchFileRow.Source); + patchRow[2] = (int)patchFile.Length; + patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; + + string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; + if (MsiInterop.MsiMaxStreamNameLength < streamName.Length) + { + streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); + Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); + if (0 == patchHeadersTable.Rows.Count) + { + patchHeadersTable.Operation = TableOperation.Add; + } + Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); + patchHeadersRow[0] = streamName; + patchHeadersRow[1] = patchFileRow.Patch; + patchRow[5] = streamName; + patchHeadersRow.Operation = RowOperation.Add; + } + else + { + patchRow[4] = patchFileRow.Patch; + } + patchRow.Operation = RowOperation.Add; + } + } + else + { + // TODO: throw because all transform rows should have made it into the patch + } + } + } + } + + if (copyFromPatch) + { + this.Output.Tables.Remove("Media"); + this.Output.Tables.Remove("File"); + this.Output.Tables.Remove("MsiFileHash"); + this.Output.Tables.Remove("MsiAssemblyName"); + } + } + } + finally + { + this.FileManagerCore.ActiveSubStorage = null; + } +#endif + this.FileFacades = allFileRows; + } + + /// + /// Adds the PatchFiles action to the sequence table if it does not already exist. + /// + /// The sequence table to check or modify. + /// The primary authoring transform. + /// The secondary patch transform. + /// The file row that contains information about the patched file. + private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow) + { + // Find/add PatchFiles action (also determine sequence for it). + // Search mainTransform first, then pairedTransform (pairedTransform overrides). + bool hasPatchFilesAction = false; + int seqInstallFiles = 0; + int seqDuplicateFiles = 0; + string tableName = table.ToString(); + + TestSequenceTableForPatchFilesAction( + mainTransform.Tables[tableName], + ref hasPatchFilesAction, + ref seqInstallFiles, + ref seqDuplicateFiles); + TestSequenceTableForPatchFilesAction( + pairedTransform.Tables[tableName], + ref hasPatchFilesAction, + ref seqInstallFiles, + ref seqDuplicateFiles); + if (!hasPatchFilesAction) + { + Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); + if (0 == iesTable.Rows.Count) + { + iesTable.Operation = TableOperation.Add; + } + + Row patchAction = iesTable.CreateRow(null); + WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"]; + int sequence = wixPatchAction.Sequence; + // Test for default sequence value's appropriateness + if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence)) + { + if (0 != seqDuplicateFiles) + { + if (seqDuplicateFiles < seqInstallFiles) + { + throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); + } + else + { + sequence = (seqDuplicateFiles + seqInstallFiles) / 2; + if (seqInstallFiles == sequence || seqDuplicateFiles == sequence) + { + throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); + } + } + } + else + { + sequence = seqInstallFiles + 1; + } + } + patchAction[0] = wixPatchAction.Action; + patchAction[1] = wixPatchAction.Condition; + patchAction[2] = sequence; + patchAction.Operation = RowOperation.Add; + } + } + + /// + /// Tests sequence table for PatchFiles and associated actions + /// + /// The table to test. + /// Set to true if PatchFiles action is found. Left unchanged otherwise. + /// Set to sequence value of InstallFiles action if found. Left unchanged otherwise. + /// Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise. + private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles) + { + if (null != iesTable) + { + foreach (Row iesRow in iesTable.Rows) + { + if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal)) + { + hasPatchFilesAction = true; + } + if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal)) + { + seqInstallFiles = (int)iesRow.Fields[2].Data; + } + if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal)) + { + seqDuplicateFiles = (int)iesRow.Fields[2].Data; + } + } + } + } + + /// + /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. + /// + /// The output to validate. + private void ValidateFileRowChanges(Output transform) + { + Table componentTable = transform.Tables["Component"]; + Table fileTable = transform.Tables["File"]; + + // There's no sense validating keypaths if the transform has no component or file table + if (componentTable == null || fileTable == null) + { + return; + } + + Dictionary componentKeyPath = new Dictionary(componentTable.Rows.Count); + + // Index the Component table for non-directory & non-registry key paths. + foreach (Row row in componentTable.Rows) + { + if (null != row.Fields[5].Data && + 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) + { + componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString()); + } + } + + Dictionary componentWithChangedKeyPath = new Dictionary(); + Dictionary componentWithNonKeyPathChanged = new Dictionary(); + // Verify changes in the file table, now that file diffing has occurred + foreach (FileRow row in fileTable.Rows) + { + string fileId = row.Fields[0].Data.ToString(); + string componentId = row.Fields[1].Data.ToString(); + + if (RowOperation.Modify != row.Operation) + { + continue; + } + + // If this file is the keypath of a component + if (componentKeyPath.ContainsValue(fileId)) + { + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + else + { + if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) + { + componentWithNonKeyPathChanged.Add(componentId, fileId); + } + } + } + + foreach (KeyValuePair componentFile in componentWithNonKeyPathChanged) + { + // Make sure all changes to non keypath files also had a change in the keypath. + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key)) + { + Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key])); + } + } + } + + private bool CompareFiles(string targetFile, string updatedFile) + { + bool? compared = null; + foreach (var extension in this.Extensions) + { + compared = extension.CompareFiles(targetFile, updatedFile); + + if (compared.HasValue) + { + break; + } + } + + if (!compared.HasValue) + { + throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. + } + + return compared.Value; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs new file mode 100644 index 00000000..02015744 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs @@ -0,0 +1,499 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Threading; + using WixToolset.Core.Bind; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + + /// + /// Creates cabinet files. + /// + internal class CreateCabinetsCommand + { + public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB + public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) + + private List fileTransfers; + + private FileSplitCabNamesCallback newCabNamesCallBack; + + private Dictionary lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence + + public CreateCabinetsCommand() + { + this.fileTransfers = new List(); + + this.newCabNamesCallBack = this.NewCabNamesCallBack; + } + + /// + /// Sets the number of threads to use for cabinet creation. + /// + public int CabbingThreadCount { private get; set; } + + public string CabCachePath { private get; set; } + + public string TempFilesLocation { private get; set; } + + /// + /// Sets the default compression level to use for cabinets + /// that don't have their compression level explicitly set. + /// + public CompressionLevel DefaultCompressionLevel { private get; set; } + + public IEnumerable BackendExtensions { private get; set; } + + public Output Output { private get; set; } + + public string LayoutDirectory { private get; set; } + + public bool Compressed { private get; set; } + + public Dictionary> FileRowsByCabinet { private get; set; } + + public Func ResolveMedia { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public Table WixMediaTable { private get; set; } + + public IEnumerable FileTransfers => this.fileTransfers; + + /// Output to generate image for. + /// Array of files to be transfered. + /// The directory in which the image should be layed out. + /// Flag if source image should be compressed. + /// The uncompressed file rows. + public void Execute() + { + RowDictionary wixMediaRows = new RowDictionary(this.WixMediaTable); + + this.lastCabinetAddedToMediaTable = new Dictionary(); + + this.SetCabbingThreadCount(); + + // Send Binder object to Facilitate NewCabNamesCallBack Callback + CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); + + // Supply Compile MediaTemplate Attributes to Cabinet Builder + int MaximumCabinetSizeForLargeFileSplitting; + int MaximumUncompressedMediaSize; + this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize); + cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting; + cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize; + + foreach (var entry in this.FileRowsByCabinet) + { + MediaRow mediaRow = entry.Key; + IEnumerable files = entry.Value; + CompressionLevel compressionLevel = this.DefaultCompressionLevel; + + WixMediaRow wixMediaRow = null; + string mediaLayoutFolder = null; + + if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) + { + mediaLayoutFolder = wixMediaRow.Layout; + + if (wixMediaRow.CompressionLevel.HasValue) + { + compressionLevel = wixMediaRow.CompressionLevel.Value; + } + } + + string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); + + CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers); + if (null != cabinetWorkItem) + { + cabinetBuilder.Enqueue(cabinetWorkItem); + } + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + // create queued cabinets with multiple threads + cabinetBuilder.CreateQueuedCabinets(); + if (Messaging.Instance.EncounteredError) + { + return; + } + } + + /// + /// Sets the thead count to the number of processors if the current thread count is set to 0. + /// + /// The thread count value must be greater than 0 otherwise and exception will be thrown. + private void SetCabbingThreadCount() + { + // default the number of cabbing threads to the number of processors if it wasn't specified + if (0 == this.CabbingThreadCount) + { + string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); + + try + { + if (null != numberOfProcessors) + { + this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat); + + if (0 >= this.CabbingThreadCount) + { + throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); + } + } + else // default to 1 if the environment variable is not set + { + this.CabbingThreadCount = 1; + } + + Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); + } + catch (ArgumentException) + { + throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); + } + catch (FormatException) + { + throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); + } + } + } + + + /// + /// Creates a work item to create a cabinet. + /// + /// Output for the current database. + /// Directory to create cabinet in. + /// MediaRow containing information about the cabinet. + /// Collection of files in this cabinet. + /// Array of files to be transfered. + /// created CabinetWorkItem object + private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable fileFacades, List fileTransfers) + { + CabinetWorkItem cabinetWorkItem = null; + string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); + + // check for an empty cabinet + if (!fileFacades.Any()) + { + string cabinetName = mediaRow.Cabinet; + + // remove the leading '#' from the embedded cabinet name to make the warning easier to understand + if (cabinetName.StartsWith("#", StringComparison.Ordinal)) + { + cabinetName = cabinetName.Substring(1); + } + + // If building a patch, remind them to run -p for torch. + if (OutputType.Patch == output.Type) + { + Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true)); + } + else + { + Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName)); + } + } + + var cabinetResolver = new CabinetResolver(this.CabCachePath, this.BackendExtensions); + + ResolvedCabinet resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); + + // create a cabinet work item if it's not being skipped + if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) + { + int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet). + + cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/); + } + else // reuse the cabinet from the cabinet cache. + { + Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path)); + + try + { + // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The + // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that + // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from + // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) + // causing the project to look like it perpetually needs a rebuild until all of the reused + // cabinets get newer timestamps. + File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); + } + catch (Exception e) + { + Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message)); + } + } + + if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) + { + Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]); + + Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers); + streamRow[0] = mediaRow.Cabinet.Substring(1); + streamRow[1] = resolvedCabinet.Path; + } + else + { + string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); + FileTransfer transfer; + if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer)) + { + transfer.Built = true; + fileTransfers.Add(transfer); + } + } + + return cabinetWorkItem; + } + + //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) + //{ + // ResolvedCabinet resolved = null; + + // List filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); + + // foreach (var extension in this.BackendExtensions) + // { + // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); + // if (null != resolved) + // { + // break; + // } + // } + + // return resolved; + //} + + /// + /// Delegate for Cabinet Split Callback + /// + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); + + /// + /// Call back to Add File Transfer for new Cab and add new Cab to Media table + /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe + /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored + /// + /// The name of splitting cabinet without extention e.g. "cab1". + /// The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab" + /// The file token of the first file present in the splitting cabinet + internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) + { + // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads + Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); + try + { + if (!mutex.WaitOne(0, false)) // Check if you can get the lock + { + // Cound not get the Lock + Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel()); + mutex.WaitOne(); // Wait on other thread + } + + string firstCabinetName = firstCabName + ".cab"; + string newCabinetName = newCabName; + bool transferAdded = false; // Used for Error Handling + + // Create File Transfer for new Cabinet using transfer of Base Cabinet + foreach (FileTransfer transfer in this.FileTransfers) + { + if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) + { + string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); + string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); + + FileTransfer newTransfer; + if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer)) + { + newTransfer.Built = true; + this.fileTransfers.Add(newTransfer); + transferAdded = true; + break; + } + } + } + + // Check if File Transfer was added + if (!transferAdded) + { + throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); + } + + // Add the new Cabinets to media table using LastSequence of Base Cabinet + Table mediaTable = this.Output.Tables["Media"]; + Table wixFileTable = this.Output.Tables["WixFile"]; + int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain + int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain + bool lastSplitCabinetFound = false; // Used for Error Handling + + string lastCabinetOfThisSequence = String.Empty; + // Get the Value of Last Cabinet Added in this split Sequence from Dictionary + if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) + { + // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence + lastCabinetOfThisSequence = firstCabinetName; + } + + foreach (MediaRow mediaRow in mediaTable.Rows) + { + // Get details for the Last Cabinet Added in this Split Sequence + if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) + { + lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; + diskIDForLastSplitCabAdded = mediaRow.DiskId; + lastSplitCabinetFound = true; + } + + // Check for Name Collision for the new Cabinet added + if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) + { + // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row + throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); + } + } + + // Check if the last Split Cabinet was found in the Media Table + if (!lastSplitCabinetFound) + { + throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); + } + + // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort + // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with + // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction + MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); + newMediaRow.Cabinet = newCabinetName; + newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion + newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; + + // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique + foreach (MediaRow mediaRow in mediaTable.Rows) + { + // Check if this row comes after inserted row and it is not the new cabinet inserted row + if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) + { + mediaRow.DiskId++; // Increment DiskID + } + } + + // Now Increment DiskID for All files Rows so that they refer to the right Media Row + foreach (WixFileRow wixFileRow in wixFileTable.Rows) + { + // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet + // This check will work as we have only one large file in every splitting cabinet + // If we want to support splitting cabinet with more large files we need to update this code + if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) + { + wixFileRow.DiskId++; // Increment DiskID + } + } + + // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback + this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; + + mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails + } + finally + { + // Releasing the Mutex here + mutex.ReleaseMutex(); + } + } + + + /// + /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides + /// + /// Output to generate image for. + /// The indexed file rows. + private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) + { + // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size + string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); + string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + int maxCabSizeForLargeFileInMB = 0; + int maxPreCompressedSizeInMB = 0; + ulong testOverFlow = 0; + + // Supply Compile MediaTemplate Attributes to Cabinet Builder + Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; + if (mediaTemplateTable != null) + { + WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; + + // Get the Value for Max Cab Size for File Splitting + try + { + // Override authored mcslfs value if environment variable is authored. + if (!String.IsNullOrEmpty(mcslfsString)) + { + maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString); + } + else + { + maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting; + } + testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; + } + catch (FormatException) + { + throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); + } + catch (OverflowException) + { + throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); + } + + try + { + // Override authored mums value if environment variable is authored. + if (!String.IsNullOrEmpty(mumsString)) + { + maxPreCompressedSizeInMB = Int32.Parse(mumsString); + } + else + { + maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; + } + testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; + } + catch (FormatException) + { + throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); + } + catch (OverflowException) + { + throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); + } + + maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; + maxUncompressedMediaSize = maxPreCompressedSizeInMB; + } + else + { + maxCabSizeForLargeFileSplitting = 0; + maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs new file mode 100644 index 00000000..767671b8 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs @@ -0,0 +1,87 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Rows; + + /// + /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. + /// + internal class CreateDeltaPatchesCommand + { + public IEnumerable FileFacades { private get; set; } + + public Table WixPatchIdTable { private get; set; } + + public string TempFilesLocation { private get; set; } + + public void Execute() + { + bool optimizePatchSizeForLargeFiles = false; + PatchSymbolFlagsType apiPatchingSymbolFlags = 0; + + if (null != this.WixPatchIdTable) + { + Row row = this.WixPatchIdTable.Rows[0]; + if (null != row) + { + if (null != row[2]) + { + optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture)); + } + + if (null != row[3]) + { + apiPatchingSymbolFlags = (PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture); + } + } + } + + foreach (FileFacade facade in this.FileFacades) + { + if (RowOperation.Modify == facade.File.Operation && + 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) + { + string deltaBase = String.Concat("delta_", facade.File.File); + string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf")); + string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd")); + + bool retainRangeWarning = false; + + if (PatchAPI.PatchInterop.CreateDelta( + deltaFile, + facade.WixFile.Source, + facade.DeltaPatchFile.Symbols, + facade.DeltaPatchFile.RetainOffsets, + new[] { facade.WixFile.PreviousSource }, + facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), + apiPatchingSymbolFlags, + optimizePatchSizeForLargeFiles, + out retainRangeWarning)) + { + PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); + + facade.WixFile.Source = deltaFile; + facade.WixFile.DeltaPatchHeaderSource = headerFile; + } + + if (retainRangeWarning) + { + // TODO: get patch family to add to warning message for PatchWiz parity. + Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); + } + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs new file mode 100644 index 00000000..aef130b0 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs @@ -0,0 +1,68 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class CreateSpecialPropertiesCommand + { + public Table PropertyTable { private get; set; } + + public Table WixPropertyTable { private get; set; } + + public void Execute() + { + // Create the special properties. + if (null != this.WixPropertyTable) + { + // Create lists of the properties that contribute to the special lists of properties. + SortedSet adminProperties = new SortedSet(); + SortedSet secureProperties = new SortedSet(); + SortedSet hiddenProperties = new SortedSet(); + + foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows) + { + if (wixPropertyRow.Admin) + { + adminProperties.Add(wixPropertyRow.Id); + } + + if (wixPropertyRow.Hidden) + { + hiddenProperties.Add(wixPropertyRow.Id); + } + + if (wixPropertyRow.Secure) + { + secureProperties.Add(wixPropertyRow.Id); + } + } + + Table propertyTable = this.PropertyTable; + if (0 < adminProperties.Count) + { + PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); + row.Property = "AdminProperties"; + row.Value = String.Join(";", adminProperties); + } + + if (0 < secureProperties.Count) + { + PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); + row.Property = "SecureCustomProperties"; + row.Value = String.Join(";", secureProperties); + } + + if (0 < hiddenProperties.Count) + { + PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); + row.Property = "MsiHiddenProperties"; + row.Value = String.Join(";", hiddenProperties); + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs new file mode 100644 index 00000000..ae76037d --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs @@ -0,0 +1,226 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.MergeMod; + using WixToolset.Msi; + using WixToolset.Core.Native; + using WixToolset.Core.Bind; + using WixToolset.Core.Cab; + + /// + /// Retrieve files information and extract them from merge modules. + /// + internal class ExtractMergeModuleFilesCommand + { + public IEnumerable FileFacades { private get; set; } + + public Table FileTable { private get; set; } + + public Table WixFileTable { private get; set; } + + public Table WixMergeTable { private get; set; } + + public int OutputInstallerVersion { private get; set; } + + public bool SuppressLayout { private get; set; } + + public string TempFilesLocation { private get; set; } + + public IEnumerable MergeModulesFileFacades { get; private set; } + + public void Execute() + { + List mergeModulesFileFacades = new List(); + + IMsmMerge2 merge = MsmInterop.GetMsmMerge(); + + // Index all of the file rows to be able to detect collisions with files in the Merge Modules. + // It may seem a bit expensive to build up this index solely for the purpose of checking collisions + // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out + // there are other cases where we need all the file rows indexed, however they are not common cases. + // 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. + Dictionary indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal); + + foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows) + { + bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); + + // If the module has files and creating layout + if (containsFiles && !this.SuppressLayout) + { + this.ExtractFilesFromMergeModule(merge, wixMergeRow); + } + } + + this.MergeModulesFileFacades = mergeModulesFileFacades; + } + + private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List mergeModulesFileFacades, Dictionary indexedFileFacades) + { + bool containsFiles = false; + + try + { + // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. + using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) + { + if (db.TableExists("File") && db.TableExists("Component")) + { + Dictionary uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); + + using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) + { + // add each file row from the merge module into the file row collection (check for errors along the way) + while (true) + { + using (Record record = view.Fetch()) + { + if (null == record) + { + break; + } + + // NOTE: this is very tricky - the merge module file rows are not added to the + // file table because they should not be created via idt import. Instead, these + // rows are created by merging in the actual modules. + FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); + fileRow.File = record[1]; + fileRow.Compressed = wixMergeRow.FileCompression; + + WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); + wixFileRow.Directory = record[2]; + wixFileRow.DiskId = wixMergeRow.DiskId; + wixFileRow.PatchGroup = -1; + wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]); + + FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow); + + FileFacade collidingFacade; + + // If case-sensitive collision with another merge module or a user-authored file identifier. + if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) + { + Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File)); + } + else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module + { + Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File)); + } + else // no collision + { + mergeModulesFileFacades.Add(mergeModuleFileFacade); + + // Keep updating the indexes as new rows are added. + indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); + uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); + } + + containsFiles = true; + } + } + } + } + + // Get the summary information to detect the Schema + using (SummaryInformation summaryInformation = new SummaryInformation(db)) + { + string moduleInstallerVersionString = summaryInformation.GetProperty(14); + + try + { + int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); + if (moduleInstallerVersion > this.OutputInstallerVersion) + { + Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion)); + } + } + catch (FormatException) + { + throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); + } + } + } + } + catch (FileNotFoundException) + { + throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); + } + catch (Win32Exception) + { + throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); + } + + return containsFiles; + } + + private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow) + { + bool moduleOpen = false; + short mergeLanguage; + + try + { + mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); + } + catch (System.FormatException) + { + Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); + return; + } + + try + { + merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); + moduleOpen = true; + + string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat); + + // extract the module cabinet, then explode all of the files to a temp directory + string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); + merge.ExtractCAB(moduleCabPath); + + string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); + Directory.CreateDirectory(mergeIdPath); + + using (var extractCab = new WixExtractCab()) + { + try + { + extractCab.Extract(moduleCabPath, mergeIdPath); + } + catch (FileNotFoundException) + { + throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); + } + catch + { + throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); + } + } + } + catch (COMException ce) + { + throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); + } + finally + { + if (moduleOpen) + { + merge.CloseModule(); + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs new file mode 100644 index 00000000..26d254f2 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -0,0 +1,332 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Text; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Msi; + using WixToolset.Core.Native; + + internal class GenerateDatabaseCommand + { + public int Codepage { private get; set; } + + public IEnumerable Extensions { private get; set; } + + /// + /// Whether to keep columns added in a transform. + /// + public bool KeepAddedColumns { private get; set; } + + public Output Output { private get; set; } + + public string OutputPath { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public string TempFilesLocation { private get; set; } + + /// + /// Whether to use a subdirectory based on the file name for intermediate files. + /// + public bool SuppressAddingValidationRows { private get; set; } + + public bool UseSubDirectory { private get; set; } + + public void Execute() + { + // Add the _Validation rows. + if (!this.SuppressAddingValidationRows) + { + Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); + + foreach (Table table in this.Output.Tables) + { + if (!table.Definition.Unreal) + { + // Add the validation rows for this table. + table.Definition.AddValidationRows(validationTable); + } + } + } + + // Set the base directory. + string baseDirectory = this.TempFilesLocation; + + if (this.UseSubDirectory) + { + string filename = Path.GetFileNameWithoutExtension(this.OutputPath); + baseDirectory = Path.Combine(baseDirectory, filename); + + // make sure the directory exists + Directory.CreateDirectory(baseDirectory); + } + + try + { + OpenDatabase type = OpenDatabase.CreateDirect; + + // set special flag for patch files + if (OutputType.Patch == this.Output.Type) + { + type |= OpenDatabase.OpenPatchFile; + } + +#if DEBUG + Console.WriteLine("Opening database at: {0}", this.OutputPath); +#endif + + using (Database db = new Database(this.OutputPath, type)) + { + // Localize the codepage if a value was specified directly. + if (-1 != this.Codepage) + { + this.Output.Codepage = this.Codepage; + } + + // if we're not using the default codepage, import a new one into our + // database before we add any tables (or the tables would be added + // with the wrong codepage). + if (0 != this.Output.Codepage) + { + this.SetDatabaseCodepage(db, this.Output.Codepage); + } + + foreach (Table table in this.Output.Tables) + { + Table importTable = table; + bool hasBinaryColumn = false; + + // Skip all unreal tables other than _Streams. + if (table.Definition.Unreal && "_Streams" != table.Name) + { + continue; + } + + // Do not put the _Validation table in patches, it is not needed. + if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name) + { + continue; + } + + // The only way to import binary data is to copy it to a local subdirectory first. + // To avoid this extra copying and perf hit, import an empty table with the same + // definition and later import the binary data from source using records. + foreach (ColumnDefinition columnDefinition in table.Definition.Columns) + { + if (ColumnType.Object == columnDefinition.Type) + { + importTable = new Table(table.Section, table.Definition); + hasBinaryColumn = true; + break; + } + } + + // Create the table via IDT import. + if ("_Streams" != importTable.Name) + { + try + { + db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); + } + catch (WixInvalidIdtException) + { + // If ValidateRows finds anything it doesn't like, it throws + importTable.ValidateRows(); + + // Otherwise we rethrow the InvalidIdt + throw; + } + } + + // insert the rows via SQL query if this table contains object fields + if (hasBinaryColumn) + { + StringBuilder query = new StringBuilder("SELECT "); + + // Build the query for the view. + bool firstColumn = true; + foreach (ColumnDefinition columnDefinition in table.Definition.Columns) + { + if (!firstColumn) + { + query.Append(","); + } + + query.AppendFormat(" `{0}`", columnDefinition.Name); + firstColumn = false; + } + query.AppendFormat(" FROM `{0}`", table.Name); + + using (View tableView = db.OpenExecuteView(query.ToString())) + { + // Import each row containing a stream + foreach (Row row in table.Rows) + { + using (Record record = new Record(table.Definition.Columns.Count)) + { + StringBuilder streamName = new StringBuilder(); + bool needStream = false; + + // the _Streams table doesn't prepend the table name (or a period) + if ("_Streams" != table.Name) + { + streamName.Append(table.Name); + } + + for (int i = 0; i < table.Definition.Columns.Count; i++) + { + ColumnDefinition columnDefinition = table.Definition.Columns[i]; + + switch (columnDefinition.Type) + { + case ColumnType.Localized: + case ColumnType.Preserved: + case ColumnType.String: + if (columnDefinition.PrimaryKey) + { + if (0 < streamName.Length) + { + streamName.Append("."); + } + streamName.Append((string)row[i]); + } + + record.SetString(i + 1, (string)row[i]); + break; + case ColumnType.Number: + record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); + break; + case ColumnType.Object: + if (null != row[i]) + { + needStream = true; + try + { + record.SetStream(i + 1, (string)row[i]); + } + catch (Win32Exception e) + { + if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME + { + throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); + } + else + { + throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); + } + } + } + break; + } + } + + // stream names are created by concatenating the name of the table with the values + // of the primary key (delimited by periods) + // check for a stream name that is more than 62 characters long (the maximum allowed length) + if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) + { + Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); + } + else // add the row to the database + { + tableView.Modify(ModifyView.Assign, record); + } + } + } + } + + // Remove rows from the _Streams table for wixpdbs. + if ("_Streams" == table.Name) + { + table.Rows.Clear(); + } + } + } + + // Insert substorages (usually transforms inside a patch or instance transforms in a package). + if (0 < this.Output.SubStorages.Count) + { + using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) + { + foreach (SubStorage subStorage in this.Output.SubStorages) + { + string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); + + // Bind the transform. + this.BindTransform(subStorage.Data, transformFile); + + if (Messaging.Instance.EncounteredError) + { + continue; + } + + // add the storage + using (Record record = new Record(2)) + { + record.SetString(1, subStorage.Name); + record.SetStream(2, transformFile); + storagesView.Modify(ModifyView.Assign, record); + } + } + } + } + + // We're good, commit the changes to the new database. + db.Commit(); + } + } + catch (IOException) + { + // TODO: this error message doesn't seem specific enough + throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath); + } + } + + private void BindTransform(Output transform, string outputPath) + { + BindTransformCommand command = new BindTransformCommand(); + command.Extensions = this.Extensions; + command.TempFilesLocation = this.TempFilesLocation; + command.Transform = transform; + command.OutputPath = outputPath; + command.TableDefinitions = this.TableDefinitions; + command.Execute(); + } + + /// + /// Sets the codepage of a database. + /// + /// Database to set codepage into. + /// Output with the codepage for the database. + private void SetDatabaseCodepage(Database db, int codepage) + { + // write out the _ForceCodepage IDT file + string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"); + using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) + { + idtFile.WriteLine(); // dummy column name record + idtFile.WriteLine(); // dummy column definition record + idtFile.Write(codepage); + idtFile.WriteLine("\t_ForceCodepage"); + } + + // try to import the table into the MSI + try + { + db.Import(idtPath); + } + catch (WixInvalidIdtException) + { + // the IDT should be valid, so an invalid code page was given + throw new WixException(WixErrors.IllegalCodepage(codepage)); + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs new file mode 100644 index 00000000..caf8b7a7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs @@ -0,0 +1,149 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class GetFileFacadesCommand + { + public Table FileTable { private get; set; } + + public Table WixFileTable { private get; set; } + + public Table WixDeltaPatchFileTable { private get; set; } + + public Table WixDeltaPatchSymbolPathsTable { private get; set; } + + public List FileFacades { get; private set; } + + public void Execute() + { + List facades = new List(this.FileTable.Rows.Count); + + RowDictionary wixFiles = new RowDictionary(this.WixFileTable); + RowDictionary deltaPatchFiles = new RowDictionary(this.WixDeltaPatchFileTable); + + foreach (FileRow file in this.FileTable.Rows) + { + WixDeltaPatchFileRow deltaPatchFile = null; + + deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile); + + facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile)); + } + + if (null != this.WixDeltaPatchSymbolPathsTable) + { + this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); + } + + this.FileFacades = facades; + } + + /// + /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. + /// + public RowDictionary ResolveDeltaPatchSymbolPaths(RowDictionary deltaPatchFiles, IEnumerable facades) + { + ILookup filesByComponent = null; + ILookup filesByDirectory = null; + ILookup filesByDiskId = null; + + foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs().OrderBy(r => r.Type)) + { + switch (row.Type) + { + case SymbolPathType.File: + this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]); + break; + + case SymbolPathType.Component: + if (null == filesByComponent) + { + filesByComponent = facades.ToLookup(f => f.File.Component); + } + + foreach (FileFacade facade in filesByComponent[row.Id]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); + } + break; + + case SymbolPathType.Directory: + if (null == filesByDirectory) + { + filesByDirectory = facades.ToLookup(f => f.WixFile.Directory); + } + + foreach (FileFacade facade in filesByDirectory[row.Id]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); + } + break; + + case SymbolPathType.Media: + if (null == filesByDiskId) + { + filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture)); + } + + foreach (FileFacade facade in filesByDiskId[row.Id]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); + } + break; + + case SymbolPathType.Product: + foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values) + { + this.MergeSymbolPaths(row, fileRow); + } + break; + + default: + // error + break; + } + } + + return deltaPatchFiles; + } + + /// + /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. + /// + /// Row from the WixPatchSymbolsPaths table. + /// FileRow into which to set symbol information. + /// This includes PreviousData as well. + private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file) + { + if (null == file.Symbols) + { + file.Symbols = row.SymbolPaths; + } + else + { + file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths); + } + + Field field = row.Fields[2]; + if (null != field.PreviousData) + { + if (null == file.PreviousSymbols) + { + file.PreviousSymbols = field.PreviousData; + } + else + { + file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs new file mode 100644 index 00000000..624cbb43 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -0,0 +1,351 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using System.Xml; + using System.Xml.XPath; + using WixToolset.Clr.Interop; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.MergeMod; + using WixToolset.Msi; + using WixToolset.Core.Native; + using WixToolset.Core.Bind; + + /// + /// Update file information. + /// + internal class MergeModulesCommand + { + public IEnumerable FileFacades { private get; set; } + + public Output Output { private get; set; } + + public string OutputPath { private get; set; } + + public IEnumerable SuppressedTableNames { private get; set; } + + public string TempFilesLocation { private get; set; } + + public void Execute() + { + Debug.Assert(OutputType.Product == this.Output.Type); + + Table wixMergeTable = this.Output.Tables["WixMerge"]; + Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"]; + + // check for merge rows to see if there is any work to do + if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) + { + return; + } + + IMsmMerge2 merge = null; + bool commit = true; + bool logOpen = false; + bool databaseOpen = false; + string logPath = null; + try + { + merge = MsmInterop.GetMsmMerge(); + + logPath = Path.Combine(this.TempFilesLocation, "merge.log"); + merge.OpenLog(logPath); + logOpen = true; + + merge.OpenDatabase(this.OutputPath); + databaseOpen = true; + + // process all the merge rows + foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) + { + bool moduleOpen = false; + + try + { + short mergeLanguage; + + try + { + mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); + } + catch (System.FormatException) + { + Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); + continue; + } + + Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); + merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); + moduleOpen = true; + + // If there is merge configuration data, create a callback object to contain it all. + ConfigurationCallback callback = null; + if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) + { + callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); + } + + // merge the module into the database that's being built + Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); + merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); + + // connect any non-primary features + if (null != wixFeatureModulesTable) + { + foreach (Row row in wixFeatureModulesTable.Rows) + { + if (wixMergeRow.Id == (string)row[1]) + { + Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); + merge.Connect((string)row[0]); + } + } + } + } + catch (COMException) + { + commit = false; + } + finally + { + IMsmErrors mergeErrors = merge.Errors; + + // display all the errors encountered during the merge operations for this module + for (int i = 1; i <= mergeErrors.Count; i++) + { + IMsmError mergeError = mergeErrors[i]; + StringBuilder databaseKeys = new StringBuilder(); + StringBuilder moduleKeys = new StringBuilder(); + + // build a string of the database keys + for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) + { + if (1 != j) + { + databaseKeys.Append(';'); + } + databaseKeys.Append(mergeError.DatabaseKeys[j]); + } + + // build a string of the module keys + for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) + { + if (1 != j) + { + moduleKeys.Append(';'); + } + moduleKeys.Append(mergeError.ModuleKeys[j]); + } + + // display the merge error based on the msm error type + switch (mergeError.Type) + { + case MsmErrorType.msmErrorExclusion: + Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); + break; + case MsmErrorType.msmErrorFeatureRequired: + Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); + break; + case MsmErrorType.msmErrorLanguageFailed: + Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorLanguageUnsupported: + Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorResequenceMerge: + Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorTableMerge: + if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table + { + Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); + } + break; + case MsmErrorType.msmErrorPlatformMismatch: + Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); + break; + default: + Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); + break; + } + } + + if (0 >= mergeErrors.Count && !commit) + { + Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); + } + + if (moduleOpen) + { + merge.CloseModule(); + } + } + } + } + finally + { + if (databaseOpen) + { + merge.CloseDatabase(commit); + } + + if (logOpen) + { + merge.CloseLog(); + } + } + + // stop processing if an error previously occurred + if (Messaging.Instance.EncounteredError) + { + return; + } + + using (Database db = new Database(this.OutputPath, OpenDatabase.Direct)) + { + Table suppressActionTable = this.Output.Tables["WixSuppressAction"]; + + // suppress individual actions + if (null != suppressActionTable) + { + foreach (Row row in suppressActionTable.Rows) + { + if (db.TableExists((string)row[0])) + { + string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); + + using (View view = db.OpenExecuteView(query)) + { + using (Record record = view.Fetch()) + { + if (null != record) + { + Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); + view.Modify(ModifyView.Delete, record); + } + } + } + } + } + } + + // query for merge module actions in suppressed sequences and drop them + foreach (string tableName in this.SuppressedTableNames) + { + if (!db.TableExists(tableName)) + { + continue; + } + + using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) + { + while (true) + { + using (Record resultRecord = view.Fetch()) + { + if (null == resultRecord) + { + break; + } + + Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); + } + } + } + + // drop suppressed sequences + using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) + { + } + + // delete the validation rows + using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) + { + using (Record record = new Record(1)) + { + record.SetString(1, tableName); + view.Execute(record); + } + } + } + + // now update the Attributes column for the files from the Merge Modules + Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); + using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) + { + foreach (FileFacade file in this.FileFacades) + { + if (!file.FromModule) + { + continue; + } + + using (Record record = new Record(1)) + { + record.SetString(1, file.File.File); + view.Execute(record); + } + + using (Record recordUpdate = view.Fetch()) + { + if (null == recordUpdate) + { + throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); + } + + recordUpdate.SetInteger(1, file.File.Sequence); + + // update the file attributes to match the compression specified + // on the Merge element or on the Package element + int attributes = 0; + + // get the current value if its not null + if (!recordUpdate.IsNull(2)) + { + attributes = recordUpdate.GetInteger(2); + } + + if (YesNoType.Yes == file.File.Compressed) + { + // these are mutually exclusive + attributes |= MsiInterop.MsidbFileAttributesCompressed; + attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + } + else if (YesNoType.No == file.File.Compressed) + { + // these are mutually exclusive + attributes |= MsiInterop.MsidbFileAttributesNoncompressed; + attributes &= ~MsiInterop.MsidbFileAttributesCompressed; + } + else // not specified + { + Debug.Assert(YesNoType.NotSet == file.File.Compressed); + + // clear any compression bits + attributes &= ~MsiInterop.MsidbFileAttributesCompressed; + attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + } + + recordUpdate.SetInteger(2, attributes); + + view.Modify(ModifyView.Update, recordUpdate); + } + } + } + + db.Commit(); + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs new file mode 100644 index 00000000..b3c09b9e --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs @@ -0,0 +1,118 @@ +// 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.Databases +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Msi; + using WixToolset.Core.Native; + using WixToolset.Bind; + using WixToolset.Core.Bind; + using WixToolset.Data.Bind; + + /// + /// Defines the file transfers necessary to layout the uncompressed files. + /// + internal class ProcessUncompressedFilesCommand + { + public string DatabasePath { private get; set; } + + public IEnumerable FileFacades { private get; set; } + + public RowDictionary MediaRows { private get; set; } + + public string LayoutDirectory { private get; set; } + + public bool Compressed { private get; set; } + + public bool LongNamesInImage { private get; set; } + + public Func ResolveMedia { private get; set; } + + public Table WixMediaTable { private get; set; } + + public IEnumerable FileTransfers { get; private set; } + + public void Execute() + { + List fileTransfers = new List(); + + Hashtable directories = new Hashtable(); + + RowDictionary wixMediaRows = new RowDictionary(this.WixMediaTable); + + using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) + { + using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) + { + while (true) + { + using (Record directoryRecord = directoryView.Fetch()) + { + if (null == directoryRecord) + { + break; + } + + string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); + + directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); + } + } + } + + using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) + { + using (Record fileQueryRecord = new Record(1)) + { + // for each file in the array of uncompressed files + foreach (FileFacade facade in this.FileFacades) + { + MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId); + string relativeFileLayoutPath = null; + + WixMediaRow wixMediaRow = null; + string mediaLayoutFolder = null; + + if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) + { + mediaLayoutFolder = wixMediaRow.Layout; + } + + string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); + + // setup up the query record and find the appropriate file in the + // previously executed file view + fileQueryRecord[1] = facade.File.File; + fileView.Execute(fileQueryRecord); + + using (Record fileRecord = fileView.Fetch()) + { + if (null == fileRecord) + { + throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File)); + } + + relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); + } + + // finally put together the base media layout path and the relative file layout path + string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); + FileTransfer transfer; + if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer)) + { + fileTransfers.Add(transfer); + } + } + } + } + } + + this.FileTransfers = fileTransfers; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs new file mode 100644 index 00000000..7da32206 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs @@ -0,0 +1,80 @@ +// 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.Databases +{ + using System; + using System.IO; + using WixToolset.Data; + using WixToolset.Data.Rows; + + internal class UpdateControlTextCommand + { + public Table BBControlTable { private get; set; } + + public Table WixBBControlTable { private get; set; } + + public Table ControlTable { private get; set; } + + public Table WixControlTable { private get; set; } + + public void Execute() + { + if (null != this.WixBBControlTable) + { + RowDictionary bbControlRows = new RowDictionary(this.BBControlTable); + foreach (Row wixRow in this.WixBBControlTable.Rows) + { + BBControlRow bbControlRow = bbControlRows.Get(wixRow.GetPrimaryKey()); + bbControlRow.Text = this.ReadTextFile(bbControlRow.SourceLineNumbers, wixRow.FieldAsString(2)); + } + } + + if (null != this.WixControlTable) + { + RowDictionary controlRows = new RowDictionary(this.ControlTable); + foreach (Row wixRow in this.WixControlTable.Rows) + { + ControlRow controlRow = controlRows.Get(wixRow.GetPrimaryKey()); + controlRow.Text = this.ReadTextFile(controlRow.SourceLineNumbers, wixRow.FieldAsString(2)); + } + } + } + + /// + /// Reads a text file and returns the contents. + /// + /// Source line numbers for row from source. + /// Source path to file to read. + /// Text string read from file. + private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source) + { + string text = null; + + try + { + using (StreamReader reader = new StreamReader(source)) + { + text = reader.ReadToEnd(); + } + } + catch (DirectoryNotFoundException e) + { + Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (FileNotFoundException e) + { + Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (IOException e) + { + Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (NotSupportedException) + { + Messaging.Instance.OnMessage(WixErrors.FileNotFound(sourceLineNumbers, source)); + } + + return text; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs new file mode 100644 index 00000000..cd9444ee --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -0,0 +1,533 @@ +// 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.Databases +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Xml; + using System.Xml.XPath; + using WixToolset.Clr.Interop; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Msi; + + /// + /// Update file information. + /// + internal class UpdateFileFacadesCommand + { + public IEnumerable FileFacades { private get; set; } + + public IEnumerable UpdateFileFacades { private get; set; } + + public string ModularizationGuid { private get; set; } + + public Output Output { private get; set; } + + public bool OverwriteHash { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public IDictionary VariableCache { private get; set; } + + public void Execute() + { + foreach (FileFacade file in this.UpdateFileFacades) + { + this.UpdateFileFacade(file); + } + } + + private void UpdateFileFacade(FileFacade file) + { + FileInfo fileInfo = null; + try + { + fileInfo = new FileInfo(file.WixFile.Source); + } + catch (ArgumentException) + { + Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); + return; + } + catch (PathTooLongException) + { + Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); + return; + } + catch (NotSupportedException) + { + Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); + return; + } + + if (!fileInfo.Exists) + { + Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source)); + return; + } + + using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + if (Int32.MaxValue < fileStream.Length) + { + throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source)); + } + + file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); + } + + string version = null; + string language = null; + try + { + Installer.GetFileVersion(fileInfo.FullName, out version, out language); + } + catch (Win32Exception e) + { + if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND + { + throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); + } + else + { + throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); + } + } + + // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. + if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table + { + if (!this.OverwriteHash) + { + // not overwriting hash, so don't do the rest of these options. + } + else if (null != file.File.Version) + { + // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks + // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up. + // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing + // all the file rows) for a relatively uncommon situation. Let's not do that. + // + // 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 => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) + { + Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File)); + } + } + else + { + if (null != file.File.Language) + { + Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); + } + + int[] hash; + try + { + Installer.GetFileHash(fileInfo.FullName, 0, out hash); + } + catch (Win32Exception e) + { + if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND + { + throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); + } + else + { + throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); + } + } + + if (null == file.Hash) + { + Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]); + file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers); + } + + file.Hash[0] = file.File.File; + file.Hash[1] = 0; + file.Hash[2] = hash[0]; + file.Hash[3] = hash[1]; + file.Hash[4] = hash[2]; + file.Hash[5] = hash[3]; + } + } + else // update the file row with the version and language information. + { + // If no version was provided by the user, use the version from the file itself. + // This is the most common case. + if (String.IsNullOrEmpty(file.File.Version)) + { + file.File.Version = version; + } + else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, 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 + // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match + // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case + // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more + // CPU intensive search to save on the memory intensive index that wouldn't be used much. + // + // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. + // That's typically even more rare than companion files so again, no index, just search. + file.File.Version = version; + } + + if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language)) + { + Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); + } + else // override the default provided by the user (usually nothing) with the actual language from the file itself. + { + file.File.Language = language; + } + + // Populate the binder variables for this file information if requested. + if (null != this.VariableCache) + { + if (!String.IsNullOrEmpty(file.File.Version)) + { + string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)); + this.VariableCache[key] = file.File.Version; + } + + if (!String.IsNullOrEmpty(file.File.Language)) + { + string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", Common.Demodularize(this.Output.Type, ModularizationGuid, file.File.File)); + this.VariableCache[key] = file.File.Language; + } + } + } + + // If this is a CLR assembly, load the assembly and get the assembly name information + if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) + { + bool targetNetfx1 = false; + StringDictionary assemblyNameValues = new StringDictionary(); + + ClrInterop.IReferenceIdentity referenceIdentity = null; + Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; + uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity); + if (0 == result && null != referenceIdentity) + { + string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); + if (null != imageRuntimeVersion) + { + targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); + } + + string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral"; + assemblyNameValues.Add("Culture", culture); + + string name = referenceIdentity.GetAttribute(null, "Name"); + if (null != name) + { + assemblyNameValues.Add("Name", name); + } + + string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); + if (null != processorArchitecture) + { + assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); + } + + string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); + if (null != publicKeyToken) + { + bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); + + // Managed code expects "null" instead of "neutral", and + // this won't be installed to the GAC since it's not signed anyway. + assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); + assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); + } + else if (file.WixFile.AssemblyApplication == null) + { + throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component)); + } + + string assemblyVersion = referenceIdentity.GetAttribute(null, "Version"); + if (null != version) + { + assemblyNameValues.Add("Version", assemblyVersion); + } + } + else + { + Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); + return; + } + + Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + if (assemblyNameValues.ContainsKey("name")) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]); + } + + if (!String.IsNullOrEmpty(version)) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version); + } + + if (assemblyNameValues.ContainsKey("version")) + { + string assemblyVersion = assemblyNameValues["version"]; + + if (!targetNetfx1) + { + // There is a bug in v1 fusion that requires the assembly's "version" attribute + // to be equal to or longer than the "fileVersion" in length when its present; + // the workaround is to prepend zeroes to the last version number in the assembly + // version. + if (null != version && version.Length > assemblyVersion.Length) + { + string padding = new string('0', version.Length - assemblyVersion.Length); + string[] assemblyVersionNumbers = assemblyVersion.Split('.'); + + if (assemblyVersionNumbers.Length > 0) + { + assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); + assemblyVersion = String.Join(".", assemblyVersionNumbers); + } + } + } + + this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion); + } + + if (assemblyNameValues.ContainsKey("culture")) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]); + } + + if (assemblyNameValues.ContainsKey("publicKeyToken")) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); + } + + if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); + } + + if (assemblyNameValues.ContainsKey("processorArchitecture")) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); + } + + // add the assembly name to the information cache + if (null != this.VariableCache) + { + string fileId = Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File); + string key = String.Concat("assemblyfullname.", fileId); + string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); + if (assemblyNameValues.ContainsKey("processorArchitecture")) + { + assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); + } + + this.VariableCache[key] = assemblyName; + + // Add entries with the preserved case publicKeyToken + string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); + this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); + + string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); + this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; + } + } + else if (FileAssemblyType.Win32Assembly == file.WixFile.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. + FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal)); + if (null == fileManifest) + { + Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest)); + } + + string win32Type = null; + string win32Name = null; + string win32Version = null; + string win32ProcessorArchitecture = null; + string win32PublicKeyToken = null; + + // loading the dom is expensive we want more performant APIs than the DOM + // Navigator is cheaper than dom. Perhaps there is a cheaper API still. + try + { + XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source); + XPathNavigator nav = doc.CreateNavigator(); + nav.MoveToRoot(); + + // this assumes a particular schema for a win32 manifest and does not + // provide error checking if the file does not conform to schema. + // The fallback case here is that nothing is added to the MsiAssemblyName + // table for an out of tolerance Win32 manifest. Perhaps warnings needed. + if (nav.MoveToFirstChild()) + { + while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") + { + nav.MoveToNext(); + } + + if (nav.MoveToFirstChild()) + { + bool hasNextSibling = true; + while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) + { + hasNextSibling = nav.MoveToNext(); + } + if (!hasNextSibling) + { + Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source)); + return; + } + + if (nav.MoveToAttribute("type", String.Empty)) + { + win32Type = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("name", String.Empty)) + { + win32Name = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("version", String.Empty)) + { + win32Version = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("processorArchitecture", String.Empty)) + { + win32ProcessorArchitecture = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("publicKeyToken", String.Empty)) + { + win32PublicKeyToken = nav.Value; + nav.MoveToParent(); + } + } + } + } + catch (FileNotFoundException fe) + { + Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest")); + } + catch (XmlException xe) + { + Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message)); + } + + Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + if (!String.IsNullOrEmpty(win32Name)) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name); + } + + if (!String.IsNullOrEmpty(win32Version)) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version); + } + + if (!String.IsNullOrEmpty(win32Type)) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type); + } + + if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture); + } + + if (!String.IsNullOrEmpty(win32PublicKeyToken)) + { + this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken); + } + } + } + + /// + /// 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(Table assemblyNameTable, FileFacade file, 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)) + { + Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name)); + } + else + { + Row assemblyNameRow = null; + + // override directly authored value + foreach (Row row in assemblyNameTable.Rows) + { + if ((string)row[0] == file.File.Component && (string)row[1] == name) + { + assemblyNameRow = row; + break; + } + } + + // 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 && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType && + String.IsNullOrEmpty(file.WixFile.AssemblyApplication) && + !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase)) + { + Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value)); + } + + if (null == assemblyNameRow) + { + assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers); + assemblyNameRow[0] = file.File.Component; + assemblyNameRow[1] = name; + assemblyNameRow[2] = value; + + // put the MsiAssemblyName row in the same section as the related File row + assemblyNameRow.SectionId = file.File.SectionId; + + if (null == file.AssemblyNames) + { + file.AssemblyNames = new List(); + } + + file.AssemblyNames.Add(assemblyNameRow); + } + else + { + assemblyNameRow[2] = value; + } + + if (this.VariableCache != null) + { + string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant(); + this.VariableCache[key] = (string)assemblyNameRow[2]; + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs b/src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs new file mode 100644 index 00000000..4157f23a --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/CLR/Interop/CLRInterop.cs @@ -0,0 +1,147 @@ +// 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.Clr.Interop +{ + using System; + using System.Runtime.InteropServices; + + /// + /// Interop class for mscorwks.dll assembly name APIs. + /// + internal sealed class ClrInterop + { + private static readonly Guid referenceIdentityGuid = new Guid("6eaf5ace-7917-4f3c-b129-e046a9704766"); + + /// + /// Protect the constructor. + /// + private ClrInterop() + { + } + + /// + /// Represents a reference to the unique signature of a code object. + /// + [ComImport] + [Guid("6eaf5ace-7917-4f3c-b129-e046a9704766")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IReferenceIdentity + { + /// + /// Get an assembly attribute. + /// + /// Attribute namespace. + /// Attribute name. + /// The assembly attribute. + [return: MarshalAs(UnmanagedType.LPWStr)] + string GetAttribute( + [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, + [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName); + + /// + /// Set an assembly attribute. + /// + /// Attribute namespace. + /// Attribute name. + /// Attribute value. + void SetAttribute( + [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, + [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName, + [In, MarshalAs(UnmanagedType.LPWStr)] string attributeValue); + + /// + /// Get an iterator for the assembly's attributes. + /// + /// Assembly attribute enumerator. + IEnumIDENTITY_ATTRIBUTE EnumAttributes(); + + /// + /// Clone an IReferenceIdentity. + /// + /// Count of deltas. + /// The deltas. + /// Cloned IReferenceIdentity. + IReferenceIdentity Clone( + [In] IntPtr /*SIZE_T*/ countOfDeltas, + [In, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] deltas); + } + + /// + /// IEnumIDENTITY_ATTRIBUTE interface. + /// + [ComImport] + [Guid("9cdaae75-246e-4b00-a26d-b9aec137a3eb")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IEnumIDENTITY_ATTRIBUTE + { + /// + /// Gets the next attributes. + /// + /// Count of elements. + /// Array of attributes being returned. + /// The next attribute. + uint Next( + [In] uint celt, + [Out, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] attributes); + + /// + /// Copy the current attribute into a buffer. + /// + /// Number of available bytes. + /// Buffer into which attribute should be written. + /// Pointer to buffer containing the attribute. + IntPtr CurrentIntoBuffer( + [In] IntPtr /*SIZE_T*/ available, + [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data); + + /// + /// Skip past a number of elements. + /// + /// Count of elements to skip. + void Skip([In] uint celt); + + /// + /// Reset the enumeration to the beginning. + /// + void Reset(); + + /// + /// Clone this attribute enumeration. + /// + /// Clone of a IEnumIDENTITY_ATTRIBUTE. + IEnumIDENTITY_ATTRIBUTE Clone(); + } + + /// + /// Gets the guid. + /// + public static Guid ReferenceIdentityGuid + { + get { return referenceIdentityGuid; } + } + + /// + /// Gets an interface pointer to an object with the specified IID, in the assembly at the specified file path. + /// + /// A valid path to the requested assembly. + /// The IID of the interface to return. + /// The returned interface pointer. + /// The error code. + [DllImport("mscorwks.dll", CharSet = CharSet.Unicode, EntryPoint = "GetAssemblyIdentityFromFile")] + internal static extern uint GetAssemblyIdentityFromFile(System.String wszAssemblyPath, ref Guid riid, out IReferenceIdentity i); + + /// + /// Assembly attributes. Contains data about an IReferenceIdentity. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct IDENTITY_ATTRIBUTE + { + [MarshalAs(UnmanagedType.LPWStr)] + public string AttributeNamespace; + [MarshalAs(UnmanagedType.LPWStr)] + public string AttributeName; + [MarshalAs(UnmanagedType.LPWStr)] + public string AttributeValue; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Differ.cs b/src/WixToolset.Core.WindowsInstaller/Differ.cs new file mode 100644 index 00000000..bdd06d32 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Differ.cs @@ -0,0 +1,611 @@ +// 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 +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using WixToolset.Core; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + using WixToolset.Msi; + + /// + /// Creates a transform by diffing two outputs. + /// + public sealed class Differ : IMessageHandler + { + private List inspectorExtensions; + private bool showPedanticMessages; + private bool suppressKeepingSpecialRows; + private bool preserveUnchangedRows; + private const char sectionDelimiter = '/'; + private SummaryInformationStreams transformSummaryInfo; + + /// + /// Instantiates a new Differ class. + /// + public Differ() + { + this.inspectorExtensions = new List(); + } + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages + { + get { return this.showPedanticMessages; } + set { this.showPedanticMessages = value; } + } + + /// + /// Gets or sets the option to suppress keeping special rows. + /// + /// The option to suppress keeping special rows. + public bool SuppressKeepingSpecialRows + { + get { return this.suppressKeepingSpecialRows; } + set { this.suppressKeepingSpecialRows = value; } + } + + /// + /// 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. + public bool PreserveUnchangedRows + { + get { return this.preserveUnchangedRows; } + set { this.preserveUnchangedRows = value; } + } + + /// + /// Adds an extension. + /// + /// The extension to add. + public void AddExtension(IInspectorExtension extension) + { + this.inspectorExtensions.Add(extension); + } + + /// + /// Creates a transform by diffing two outputs. + /// + /// The target output. + /// The updated output. + /// The transform. + public Output Diff(Output targetOutput, Output updatedOutput) + { + return Diff(targetOutput, updatedOutput, 0); + } + + /// + /// Creates a transform by diffing two outputs. + /// + /// The target output. + /// The updated output. + /// + /// The transform. + public Output Diff(Output targetOutput, Output updatedOutput, TransformFlags validationFlags) + { + Output transform = new Output(null); + transform.Type = OutputType.Transform; + transform.Codepage = updatedOutput.Codepage; + this.transformSummaryInfo = new SummaryInformationStreams(); + + // compare the codepages + if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) + { + this.OnMessage(WixErrors.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); + if (null != updatedOutput.SourceLineNumbers) + { + this.OnMessage(WixErrors.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); + } + } + + // compare the output types + if (targetOutput.Type != updatedOutput.Type) + { + throw new WixException(WixErrors.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); + } + + // compare the contents of the tables + foreach (Table targetTable in targetOutput.Tables) + { + Table updatedTable = updatedOutput.Tables[targetTable.Name]; + TableOperation operation = TableOperation.None; + + List rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); + + if (TableOperation.Drop == operation) + { + Table droppedTable = transform.EnsureTable(targetTable.Definition); + droppedTable.Operation = TableOperation.Drop; + } + else if (TableOperation.None == operation) + { + Table modified = transform.EnsureTable(updatedTable.Definition); + rows.ForEach(r => modified.Rows.Add(r)); + } + } + + // added tables + foreach (Table updatedTable in updatedOutput.Tables) + { + if (null == targetOutput.Tables[updatedTable.Name]) + { + Table addedTable = transform.EnsureTable(updatedTable.Definition); + addedTable.Operation = TableOperation.Add; + + foreach (Row updatedRow in updatedTable.Rows) + { + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + addedTable.Rows.Add(updatedRow); + } + } + } + + // set summary information properties + if (!this.suppressKeepingSpecialRows) + { + Table summaryInfoTable = transform.Tables["_SummaryInformation"]; + this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); + } + + return transform; + } + + /// + /// Sends a message to the message delegate if there is one. + /// + /// Message event arguments. + public void OnMessage(MessageEventArgs e) + { + Messaging.Instance.OnMessage(e); + } + + /// + /// Add a row to the using the primary key. + /// + /// The indexed rows. + /// The row to index. + private void AddIndexedRow(IDictionary index, Row row) + { + string primaryKey = row.GetPrimaryKey('/'); + if (null != primaryKey) + { + // Overriding WixActionRows have a primary key defined and take precedence in the index. + if (row is WixActionRow) + { + WixActionRow currentRow = (WixActionRow)row; + if (index.Contains(primaryKey)) + { + // If the current row is not overridable, see if the indexed row is. + if (!currentRow.Overridable) + { + WixActionRow indexedRow = index[primaryKey] as WixActionRow; + if (null != indexedRow && indexedRow.Overridable) + { + // The indexed key is overridable and should be replaced + // (not removed and re-added which results in two Array.Copy + // operations for SortedList, or may be re-hashing in other + // implementations of IDictionary). + index[primaryKey] = currentRow; + } + } + + // If we got this far, the row does not need to be indexed. + return; + } + } + + // Nothing else should be added more than once. + if (!index.Contains(primaryKey)) + { + index.Add(primaryKey, row); + } + else if (this.showPedanticMessages) + { + this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); + } + } + else // use the string representation of the row as its primary key (it may not be unique) + { + // this is provided for compatibility with unreal tables with no primary key + // all real tables must specify at least one column as the primary key + primaryKey = row.ToString(); + index[primaryKey] = row; + } + } + + private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow) + { + Row comparedRow = null; + keepRow = false; + operation = RowOperation.None; + + if (null == targetRow ^ null == updatedRow) + { + if (null == targetRow) + { + operation = updatedRow.Operation = RowOperation.Add; + comparedRow = updatedRow; + } + else if (null == updatedRow) + { + operation = targetRow.Operation = RowOperation.Delete; + targetRow.SectionId = targetRow.SectionId + sectionDelimiter; + comparedRow = targetRow; + keepRow = true; + } + } + else // possibly modified + { + updatedRow.Operation = RowOperation.None; + if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) + { + // 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; + comparedRow = updatedRow; + keepRow = true; + operation = RowOperation.Modify; + } + } + else + { + if (this.preserveUnchangedRows) + { + keepRow = true; + } + + for (int i = 0; i < updatedRow.Fields.Length; i++) + { + ColumnDefinition columnDefinition = updatedRow.Fields[i].Column; + + if (!columnDefinition.PrimaryKey) + { + bool modified = false; + + if (i >= targetRow.Fields.Length) + { + columnDefinition.Added = true; + modified = true; + } + else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + if (null == targetRow[i] ^ null == updatedRow[i]) + { + modified = true; + } + else if (null != targetRow[i] && null != updatedRow[i]) + { + modified = ((int)targetRow[i] != (int)updatedRow[i]); + } + } + else if (ColumnType.Preserved == columnDefinition.Type) + { + updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data; + + // keep rows containing preserved fields so the historical data is available to the binder + keepRow = !this.suppressKeepingSpecialRows; + } + else if (ColumnType.Object == columnDefinition.Type) + { + ObjectField targetObjectField = (ObjectField)targetRow.Fields[i]; + ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i]; + + 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 + updatedObjectField.PreviousData = (string)targetObjectField.Data; + + // always remember the unresolved data for target build + updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData; + + // keep rows containing object fields so the files can be compared in the binder + keepRow = !this.suppressKeepingSpecialRows; + } + else + { + modified = ((string)targetRow[i] != (string)updatedRow[i]); + } + + if (modified) + { + if (null != updatedRow.Fields[i].PreviousData) + { + updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString(); + } + + updatedRow.Fields[i].Modified = true; + operation = updatedRow.Operation = RowOperation.Modify; + keepRow = true; + } + } + } + + if (keepRow) + { + comparedRow = updatedRow; + comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + } + } + } + + return comparedRow; + } + + private List CompareTables(Output targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) + { + List rows = new List(); + operation = TableOperation.None; + + // dropped tables + if (null == updatedTable ^ null == targetTable) + { + if (null == targetTable) + { + operation = TableOperation.Add; + rows.AddRange(updatedTable.Rows); + } + else if (null == updatedTable) + { + operation = TableOperation.Drop; + } + } + else // possibly modified tables + { + SortedList updatedPrimaryKeys = new SortedList(); + SortedList targetPrimaryKeys = new SortedList(); + + // compare the table definitions + if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) + { + // continue to the next table; may be more mismatches + this.OnMessage(WixErrors.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); + } + else + { + this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); + + // diff the target and updated rows + foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys) + { + string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key; + bool keepRow = false; + RowOperation rowOperation = RowOperation.None; + + Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow); + + if (keepRow) + { + rows.Add(compared); + } + } + + // find the inserted rows + foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys) + { + string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key; + + if (!targetPrimaryKeys.Contains(updatedPrimaryKey)) + { + Row updatedRow = (Row)updatedPrimaryKeyEntry.Value; + + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + rows.Add(updatedRow); + } + } + } + } + + return rows; + } + + private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys) + { + // index the target rows + foreach (Row row in targetTable.Rows) + { + this.AddIndexedRow(targetPrimaryKeys, row); + + if ("Property" == targetTable.Name) + { + if ("ProductCode" == (string)row[0]) + { + this.transformSummaryInfo.TargetProductCode = (string)row[1]; + if ("*" == this.transformSummaryInfo.TargetProductCode) + { + this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == (string)row[0]) + { + this.transformSummaryInfo.TargetProductVersion = (string)row[1]; + } + else if ("UpgradeCode" == (string)row[0]) + { + this.transformSummaryInfo.TargetUpgradeCode = (string)row[1]; + } + } + else if ("_SummaryInformation" == targetTable.Name) + { + if (1 == (int)row[0]) // PID_CODEPAGE + { + this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1]; + } + else if (7 == (int)row[0]) // PID_TEMPLATE + { + this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1]; + } + else if (14 == (int)row[0]) // PID_PAGECOUNT + { + this.transformSummaryInfo.TargetMinimumVersion = (string)row[1]; + } + } + } + + // index the updated rows + foreach (Row row in updatedTable.Rows) + { + this.AddIndexedRow(updatedPrimaryKeys, row); + + if ("Property" == updatedTable.Name) + { + if ("ProductCode" == (string)row[0]) + { + this.transformSummaryInfo.UpdatedProductCode = (string)row[1]; + if ("*" == this.transformSummaryInfo.UpdatedProductCode) + { + this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == (string)row[0]) + { + this.transformSummaryInfo.UpdatedProductVersion = (string)row[1]; + } + } + else if ("_SummaryInformation" == updatedTable.Name) + { + if (1 == (int)row[0]) // PID_CODEPAGE + { + this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1]; + } + else if (7 == (int)row[0]) // PID_TEMPLATE + { + this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1]; + } + else if (14 == (int)row[0]) // PID_PAGECOUNT + { + this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1]; + } + } + } + } + + private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) + { + // calculate the minimum version of MSI required to process the transform + int targetMin; + int updatedMin; + int minimumVersion = 100; + + if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin)) + { + minimumVersion = Math.Max(targetMin, updatedMin); + } + + Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count); + foreach (Row row in summaryInfoTable.Rows) + { + summaryRows[row[0]] = row; + + if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) + { + row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; + row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0]) + { + row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) + { + row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) + { + row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); + } + else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0]) + { + row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Security == (int)row[0]) + { + row[1] = "4"; + } + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; + summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + } + + private class SummaryInformationStreams + { + public string TargetSummaryInfoCodepage + { get; set; } + + public string TargetPlatformAndLanguage + { get; set; } + + public string TargetProductCode + { get; set; } + + public string TargetProductVersion + { get; set; } + + public string TargetUpgradeCode + { get; set; } + + public string TargetMinimumVersion + { get; set; } + + public string UpdatedSummaryInfoCodepage + { get; set; } + + public string UpdatedPlatformAndLanguage + { get; set; } + + public string UpdatedProductCode + { get; set; } + + public string UpdatedProductVersion + { get; set; } + + public string UpdatedMinimumVersion + { get; set; } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs new file mode 100644 index 00000000..40901d7c --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs @@ -0,0 +1,282 @@ +// 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.Inscribe +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Runtime.InteropServices; + using System.Security.Cryptography.X509Certificates; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Msi; + + internal class InscribeMsiPackageCommand + { + public InscribeMsiPackageCommand(IInscribeContext context) + { + this.Context = context; + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + } + + private IInscribeContext Context { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + public bool Execute() + { + // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered + bool foundUnsignedExternals = false; + bool shouldCommit = false; + + FileAttributes attributes = File.GetAttributes(this.Context.InputFilePath); + if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) + { + this.Context.Messaging.OnMessage(WixErrors.ReadOnlyOutputFile(this.Context.InputFilePath)); + return shouldCommit; + } + + using (Database database = new Database(this.Context.InputFilePath, OpenDatabase.Transact)) + { + // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content + int codepage = 1252; + + // list of certificates for this database (hash/identifier) + Dictionary certificates = new Dictionary(); + + // Reset the in-memory tables for this new database + Table digitalSignatureTable = new Table(null, this.TableDefinitions["MsiDigitalSignature"]); + Table digitalCertificateTable = new Table(null, this.TableDefinitions["MsiDigitalCertificate"]); + + // If any digital signature records exist that are not of the media type, preserve them + if (database.TableExists("MsiDigitalSignature")) + { + using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) + { + while (true) + { + using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) + { + if (null == digitalSignatureRecord) + { + break; + } + + Row digitalSignatureRow = null; + digitalSignatureRow = digitalSignatureTable.CreateRow(null); + + string table = digitalSignatureRecord.GetString(0); + string signObject = digitalSignatureRecord.GetString(1); + + digitalSignatureRow[0] = table; + digitalSignatureRow[1] = signObject; + digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); + + if (false == digitalSignatureRecord.IsNull(3)) + { + // Export to a file, because the MSI API's require us to provide a file path on disk + string hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); + string hashFileName = string.Concat(table, ".", signObject, ".bin"); + + Directory.CreateDirectory(hashPath); + hashPath = Path.Combine(hashPath, hashFileName); + + using (FileStream fs = File.Create(hashPath)) + { + int bytesRead; + byte[] buffer = new byte[1024 * 4]; + + while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + + digitalSignatureRow[3] = hashFileName; + } + } + } + } + } + + // If any digital certificates exist, extract and preserve them + if (database.TableExists("MsiDigitalCertificate")) + { + using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) + { + while (true) + { + using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) + { + if (null == digitalCertificateRecord) + { + break; + } + + string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate + + // Export to a file, because the MSI API's require us to provide a file path on disk + string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + Directory.CreateDirectory(certPath); + certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); + + using (FileStream fs = File.Create(certPath)) + { + int bytesRead; + byte[] buffer = new byte[1024 * 4]; + + while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + + // Add it to our "add to MsiDigitalCertificate" table dictionary + Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); + digitalCertificateRow[0] = certificateId; + + // Now set the file path on disk where this binary stream will be picked up at import time + digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); + + // Load the cert to get it's thumbprint + X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); + X509Certificate2 cert2 = new X509Certificate2(cert); + + certificates.Add(cert2.Thumbprint, certificateId); + } + } + } + } + + using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) + { + while (true) + { + using (Record mediaRecord = mediaView.Fetch()) + { + if (null == mediaRecord) + { + break; + } + + X509Certificate2 cert2 = null; + Row digitalSignatureRow = null; + + string cabName = mediaRecord.GetString(4); // get the name of the cab + // If there is no cabinet or it's an internal cab, skip it. + if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + string cabId = mediaRecord.GetString(1); // get the ID of the cab + string cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName); + + // If the cabs aren't there, throw an error but continue to catch the other errors + if (!File.Exists(cabPath)) + { + this.Context.Messaging.OnMessage(WixErrors.WixFileNotFound(cabPath)); + continue; + } + + try + { + // Get the certificate from the cab + X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); + cert2 = new X509Certificate2(signedFileCert); + } + catch (System.Security.Cryptography.CryptographicException e) + { + uint HResult = unchecked((uint)Marshal.GetHRForException(e)); + + // If the file has no cert, continue, but flag that we found at least one so we can later give a warning + if (0x80092009 == HResult) // CRYPT_E_NO_MATCH + { + foundUnsignedExternals = true; + continue; + } + + // todo: exactly which HRESULT corresponds to this issue? + // If it's one of these exact platforms, warn the user that it may be due to their OS. + if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 + (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP + { + this.Context.Messaging.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); + } + else // otherwise, generic error + { + this.Context.Messaging.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); + } + } + + // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added + if (!certificates.ContainsKey(cert2.Thumbprint)) + { + // generate a stable identifier + string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); + + // Add it to our "add to MsiDigitalCertificate" table dictionary + Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); + digitalCertificateRow[0] = certificateGeneratedId; + + // Export to a file, because the MSI API's require us to provide a file path on disk + string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + Directory.CreateDirectory(certPath); + certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); + File.Delete(certPath); + + using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) + { + writer.Write(cert2.RawData); + writer.Close(); + } + + // Now set the file path on disk where this binary stream will be picked up at import time + digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); + + certificates.Add(cert2.Thumbprint, certificateGeneratedId); + } + + digitalSignatureRow = digitalSignatureTable.CreateRow(null); + + digitalSignatureRow[0] = "Media"; + digitalSignatureRow[1] = cabId; + digitalSignatureRow[2] = certificates[cert2.Thumbprint]; + } + } + } + + if (digitalCertificateTable.Rows.Count > 0) + { + database.ImportTable(codepage, digitalCertificateTable, this.Context.IntermediateFolder, true); + shouldCommit = true; + } + + if (digitalSignatureTable.Rows.Count > 0) + { + database.ImportTable(codepage, digitalSignatureTable, this.Context.IntermediateFolder, true); + shouldCommit = true; + } + + // TODO: if we created the table(s), then we should add the _Validation records for them. + + certificates = null; + + // If we did find external cabs but none of them were signed, give a warning + if (foundUnsignedExternals) + { + this.Context.Messaging.OnMessage(WixWarnings.ExternalCabsAreNotSigned(this.Context.InputFilePath)); + } + + if (shouldCommit) + { + database.Commit(); + } + } + + return shouldCommit; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/MergeMod/NativeMethods.cs b/src/WixToolset.Core.WindowsInstaller/MergeMod/NativeMethods.cs new file mode 100644 index 00000000..daf259b4 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/MergeMod/NativeMethods.cs @@ -0,0 +1,508 @@ +// 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 false +namespace WixToolset.MergeMod +{ + using System; + using System.Collections; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Errors returned by merge operations. + /// + [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")] + internal enum MsmErrorType + { + /// + /// A request was made to open a module with a language not supported by the module. + /// No more general language is supported by the module. + /// Adds msmErrorLanguageUnsupported to the Type property and the requested language + /// to the Language Property (Error Object). All Error object properties are empty. + /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). + /// + msmErrorLanguageUnsupported = 1, + + /// + /// A request was made to open a module with a supported language but the module has + /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property + /// and the applied transform's language to the Language Property of the Error object. + /// This may not be the requested language if a more general language was used. + /// All other properties of the Error object are empty. The OpenModule function + /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). + /// + msmErrorLanguageFailed = 2, + + /// + /// The module cannot be merged because it excludes, or is excluded by, another module + /// in the database. Adds msmErrorExclusion to the Type property of the Error object. + /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the + /// excluded module's row in the ModuleExclusion table. If an existing module excludes + /// the module being merged, the excluded module's ModuleSignature information is added + /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys + /// contains the excluded module's ModuleSignature information. All other properties + /// are empty (or -1). + /// + msmErrorExclusion = 3, + + /// + /// Merge conflict during merge. The value of the Type property is set to + /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain + /// the table name and primary keys of the conflicting row in the database. The + /// ModuleTable property and ModuleKeys property contain the table name and primary keys + /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be + /// null if the row does not exist in the database. For example, if the conflict is in a + /// generated FeatureComponents table entry. On Windows Installer version 2.0, when + /// merging a configurable merge module, configuration may cause these properties to + /// refer to rows that do not exist in the module. + /// + msmErrorTableMerge = 4, + + /// + /// There was a problem resequencing a sequence table to contain the necessary merged + /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable + /// and DatabaseKeys properties contain the sequence table name and primary keys + /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties + /// contain the sequence table name and primary key (action name) of the conflicting row. + /// On Windows Installer version 2.0, when merging a configurable merge module, + /// configuration may cause these properties to refer to rows that do not exist in the module. + /// + msmErrorResequenceMerge = 5, + + /// + /// Not used. + /// + msmErrorFileCreate = 6, + + /// + /// There was a problem creating a directory to extract a file to disk. The Path property + /// contains the directory that could not be created. All other properties are empty or -1. + /// Not available with Windows Installer version 1.0. + /// + msmErrorDirCreate = 7, + + /// + /// A feature name is required to complete the merge, but no feature name was provided. + /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys + /// contain the table name and primary keys of the conflicting row. The ModuleTable and + /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged. + /// On Windows Installer version 2.0, when merging a configurable merge module, configuration + /// may cause these properties to refer to rows that do not exist in the module. + /// If the failure is in a generated FeatureComponents table, the DatabaseTable and + /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to + /// the row in the Component table causing the failure. + /// + msmErrorFeatureRequired = 8, + + /// + /// Available with Window Installer version 2.0. Substitution of a Null value into a + /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and + /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row + /// into the ModuleTable property and ModuleKeys property. All other properties of the Error + /// object are set to an empty string or -1. This error causes the immediate failure of the + /// merge and the MergeEx function to return E_FAIL. + /// + msmErrorBadNullSubstitution = 9, + + /// + /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer + /// Format Type into a Binary Type data column. This type of error returns + /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the + /// keys from the ModuleSubstitution table for this row into the ModuleTable property. + /// All other properties of the Error object are set to an empty string or -1. This error + /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. + /// + msmErrorBadSubstitutionType = 10, + + /// + /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table + /// references a configuration item not defined in the ModuleConfiguration table. + /// This type of error returns msmErrorMissingConfigItem in the Type property and enters + /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into + /// the ModuleTable property. All other properties of the Error object are set to an empty + /// string or -1. This error causes the immediate failure of the merge and the MergeEx + /// function to return E_FAIL. + /// + msmErrorMissingConfigItem = 11, + + /// + /// Available with Window Installer version 2.0. The authoring tool has returned a Null + /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this + /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution" + /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property. + /// All other properties of the Error object are set to an empty string or -1. This error + /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. + /// + msmErrorBadNullResponse = 12, + + /// + /// Available with Window Installer version 2.0. The authoring tool returned a failure code + /// (not S_OK or S_FALSE) when asked for data. An error of this type will return + /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution" + /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property. + /// All other properties of the Error object are set to an empty string or -1. This error + /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. + /// + msmErrorDataRequestFailed = 13, + + /// + /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was + /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of + /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of + /// the error object are set to an empty string or -1. This error causes the immediate failure + /// of the merge and causes the Merge function or MergeEx function to return E_FAIL. + /// + msmErrorPlatformMismatch = 14, + } + + /// + /// IMsmMerge2 interface. + /// + [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")] + internal interface IMsmMerge2 + { + /// + /// The OpenDatabase method of the Merge object opens a Windows Installer installation + /// database, located at a specified path, that is to be merged with a module. + /// + /// Path to the database being opened. + void OpenDatabase(string path); + + /// + /// The OpenModule method of the Merge object opens a Windows Installer merge module + /// in read-only mode. A module must be opened before it can be merged with an installation database. + /// + /// Fully qualified file name pointing to a merge module. + /// A valid language identifier (LANGID). + void OpenModule(string fileName, short language); + + /// + /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database. + /// + /// true if changes should be saved, false otherwise. + void CloseDatabase(bool commit); + + /// + /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module. + /// + void CloseModule(); + + /// + /// The OpenLog method of the Merge object opens a log file that receives progress and error messages. + /// If the log file already exists, the installer appends new messages. If the log file does not exist, + /// the installer creates a log file. + /// + /// Fully qualified filename pointing to a file to open or create. + void OpenLog(string fileName); + + /// + /// The CloseLog method of the Merge object closes the current log file. + /// + void CloseLog(); + + /// + /// The Log method of the Merge object writes a text string to the currently open log file. + /// + /// The text string to display. + void Log(string message); + + /// + /// Gets the errors from the last merge operation. + /// + /// The errors from the last merge operation. + IMsmErrors Errors + { + get; + } + + /// + /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. + /// + /// A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. + object Dependencies + { + get; + } + + /// + /// The Merge method of the Merge object executes a merge of the current database and current + /// module. The merge attaches the components in the module to the feature identified by Feature. + /// The root of the module's directory tree is redirected to the location given by RedirectDir. + /// + /// The name of a feature in the database. + /// The key of an entry in the Directory table of the database. + /// This parameter may be NULL or an empty string. + void Merge(string feature, string redirectDir); + + /// + /// The Connect method of the Merge object connects a module to an additional feature. + /// The module must have already been merged into the database or will be merged into the database. + /// The feature must exist before calling this function. + /// + /// The name of a feature already existing in the database. + void Connect(string feature); + + /// + /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and + /// saves it as the specified file. The installer creates this file if it does not already exist + /// and overwritten if it does exist. + /// + /// The fully qualified destination file. + void ExtractCAB(string fileName); + + /// + /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module + /// and then writes those files to the destination directory. + /// + /// The fully qualified destination directory. + void ExtractFiles(string path); + + /// + /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it + /// takes an extra argument. The Merge method executes a merge of the current database and + /// current module. The merge attaches the components in the module to the feature identified + /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir. + /// + /// The name of a feature in the database. + /// The key of an entry in the Directory table of the database. This parameter may + /// be NULL or an empty string. + /// The pConfiguration argument is an interface implemented by the client. The argument may + /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration + /// functionality, but does not obligate the client to provide configuration data for any specific configurable item. + void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration); + + /// + /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and + /// then writes those files to the destination directory. + /// + /// The fully qualified destination directory. + /// Set to specify using long file names for path segments and final file names. + /// This is a list of fully-qualified paths for the files that were successfully extracted. + /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. + void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths); + + /// + /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. + /// + /// A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. + /// Semantically, each interface in the enumerator represents an item that can be configured by the module consumer. + /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum(). + /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics. + object ConfigurableItems + { + get; + } + + /// + /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to + /// a source image on disk after a merge, taking into account changes to the module that might have been made + /// during module configuration. The list of files to be extracted is taken from the file table of the module + /// during the merge process. The list of files consists of every file successfully copied from the file table + /// of the module to the target database. File table entries that were not copied due to primary key conflicts + /// with existing rows in the database are not a part of this list. At image creation time, the directory for + /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is + /// the root of the source image for the install. fLongFileNames determines whether or not long file names are + /// used for both path segments and final file names. The function fails if no database is open, no module is + /// open, or no merge has been performed. + /// + /// The path of the root of the source image for the install. + /// Determines whether or not long file names are used for both path segments and final file names. + /// This is a list of fully-qualified paths for the files that were successfully extracted. + /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. + void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths); + + /// + /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function + /// returns the primary keys in the File table of the currently open module. The primary keys are returned + /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles. + /// + IMsmStrings ModuleFiles + { + get; + } + } + + /// + /// Collection of merge errors. + /// + [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")] + internal interface IMsmErrors + { + /// + /// Gets the IMsmError at the specified index. + /// + /// The one-based index of the IMsmError to get. + IMsmError this[int index] + { + get; + } + + /// + /// Gets the count of IMsmErrors in this collection. + /// + /// The count of IMsmErrors in this collection. + int Count + { + get; + } + } + + /// + /// A merge error. + /// + [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")] + internal interface IMsmError + { + /// + /// Gets the type of merge error. + /// + /// The type of merge error. + MsmErrorType Type + { + get; + } + + /// + /// Gets the path information from the merge error. + /// + /// The path information from the merge error. + string Path + { + get; + } + + /// + /// Gets the language information from the merge error. + /// + /// The language information from the merge error. + short Language + { + get; + } + + /// + /// Gets the database table from the merge error. + /// + /// The database table from the merge error. + string DatabaseTable + { + get; + } + + /// + /// Gets the collection of database keys from the merge error. + /// + /// The collection of database keys from the merge error. + IMsmStrings DatabaseKeys + { + get; + } + + /// + /// Gets the module table from the merge error. + /// + /// The module table from the merge error. + string ModuleTable + { + get; + } + + /// + /// Gets the collection of module keys from the merge error. + /// + /// The collection of module keys from the merge error. + IMsmStrings ModuleKeys + { + get; + } + } + + /// + /// A collection of strings. + /// + [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")] + internal interface IMsmStrings + { + /// + /// Gets the string at the specified index. + /// + /// The one-based index of the string to get. + string this[int index] + { + get; + } + + /// + /// Gets the count of strings in this collection. + /// + /// The count of strings in this collection. + int Count + { + get; + } + } + + /// + /// Callback for configurable merge modules. + /// + [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] + internal interface IMsmConfigureModule + { + /// + /// Callback to retrieve text data for configurable merge modules. + /// + /// Name of the data to be retrieved. + /// The data corresponding to the name. + /// The error code (HRESULT). + [PreserveSig] + int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData); + + /// + /// Callback to retrieve integer data for configurable merge modules. + /// + /// Name of the data to be retrieved. + /// The data corresponding to the name. + /// The error code (HRESULT). + [PreserveSig] + int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData); + } + + /// + /// Merge merge modules into an MSI file. + /// + [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")] + internal class MsmMerge2 + { + } + + /// + /// Defines the standard COM IClassFactory interface. + /// + [ComImport, Guid("00000001-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IClassFactory + { + [return:MarshalAs(UnmanagedType.IUnknown)] + object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); + } + + /// + /// Contains native methods for merge operations. + /// + internal class NativeMethods + { + [DllImport("mergemod.dll", EntryPoint="DllGetClassObject", PreserveSig=false)] + [return: MarshalAs(UnmanagedType.IUnknown)] + private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); + + /// + /// Load the merge object directly from a local mergemod.dll without going through COM registration. + /// + /// Merge interface. + internal static IMsmMerge2 GetMsmMerge() + { + IClassFactory classFactory = (IClassFactory) MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID); + return (IMsmMerge2) classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID); + } + } +} +#endif \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs b/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs new file mode 100644 index 00000000..801ebdde --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs @@ -0,0 +1,303 @@ +// 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.Msi +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Text; + using System.Threading; + using WixToolset.Data; + using WixToolset.Core.Native; + + /// + /// Wrapper class for managing MSI API database handles. + /// + internal sealed class Database : MsiHandle + { + private const int STG_E_LOCKVIOLATION = unchecked((int)0x80030021); + + /// + /// Constructor that opens an MSI database. + /// + /// Path to the database to be opened. + /// Persist mode to use when opening the database. + public Database(string path, OpenDatabase type) + { + uint handle = 0; + int error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out handle); + if (0 != error) + { + throw new MsiException(error); + } + this.Handle = handle; + } + + public void ApplyTransform(string transformFile) + { + // get the curret validation bits + TransformErrorConditions conditions = TransformErrorConditions.None; + using (SummaryInformation summaryInfo = new SummaryInformation(transformFile)) + { + string value = summaryInfo.GetProperty((int)SummaryInformation.Transform.ValidationFlags); + try + { + int validationFlags = Int32.Parse(value, CultureInfo.InvariantCulture); + conditions = (TransformErrorConditions)(validationFlags & 0xffff); + } + catch (FormatException) + { + // fallback to default of None + } + } + + this.ApplyTransform(transformFile, conditions); + } + + /// + /// Applies a transform to this database. + /// + /// Path to the transform file being applied. + /// Specifies the error conditions that are to be suppressed. + public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions) + { + int error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions); + if (0 != error) + { + throw new MsiException(error); + } + } + + /// + /// Commits changes made to the database. + /// + public void Commit() + { + // Retry this call 3 times to deal with an MSI internal locking problem. + const int retryWait = 300; + const int retryLimit = 3; + int error = 0; + + for (int i = 1; i <= retryLimit; ++i) + { + error = MsiInterop.MsiDatabaseCommit(this.Handle); + + if (0 == error) + { + return; + } + else + { + MsiException exception = new MsiException(error); + + // We need to see if the error code is contained in any of the strings in ErrorInfo. + // Join the array together and search for the error code to cover the string array. + if (!String.Join(", ", exception.ErrorInfo).Contains(STG_E_LOCKVIOLATION.ToString())) + { + break; + } + + Console.Error.WriteLine(String.Format("Failed to create the database. Info: {0}. Retrying ({1} of {2})", String.Join(", ", exception.ErrorInfo), i, retryLimit)); + Thread.Sleep(retryWait); + } + } + + throw new MsiException(error); + } + + /// + /// Creates and populates the summary information stream of an existing transform file. + /// + /// Required database that does not include the changes. + /// The name of the generated transform file. + /// Required error conditions that should be suppressed when the transform is applied. + /// Required when the transform is applied to a database; + /// shows which properties should be validated to verify that this transform can be applied to the database. + public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations) + { + int error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations); + if (0 != error) + { + throw new MsiException(error); + } + } + + /// + /// Imports an installer text archive table (idt file) into an open database. + /// + /// Specifies the path to the file to import. + /// Attempted to import an IDT file with an invalid format or unsupported data. + /// Another error occured while importing the IDT file. + public void Import(string idtPath) + { + string folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath)); + string fileName = Path.GetFileName(idtPath); + + int error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName); + if (1627 == error) // ERROR_FUNCTION_FAILED + { + throw new WixInvalidIdtException(idtPath); + } + else if (0 != error) + { + throw new MsiException(error); + } + } + + /// + /// Exports an installer table from an open database to a text archive file (idt file). + /// + /// Specifies the name of the table to export. + /// Specifies the name of the folder that contains archive files. If null or empty string, uses current directory. + /// Specifies the name of the exported table archive file. + public void Export(string tableName, string folderPath, string fileName) + { + if (null == folderPath || 0 == folderPath.Length) + { + folderPath = System.Environment.CurrentDirectory; + } + + int error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName); + if (0 != error) + { + throw new MsiException(error); + } + } + + /// + /// Creates a transform that, when applied to the reference database, results in this database. + /// + /// Required database that does not include the changes. + /// The name of the generated transform file. This is optional. + /// true if a transform is generated; false if a transform is not generated because + /// there are no differences between the two databases. + public bool GenerateTransform(Database referenceDatabase, string transformFile) + { + int error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0); + if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found + { + throw new MsiException(error); + } + + return (0xE8 != error); + } + + /// + /// Merges two databases together. + /// + /// The database to merge into the base database. + /// The name of the table to receive merge conflict information. + public void Merge(Database mergeDatabase, string tableName) + { + int error = MsiInterop.MsiDatabaseMerge(this.Handle, mergeDatabase.Handle, tableName); + if (0 != error) + { + throw new MsiException(error); + } + } + + /// + /// Prepares a database query and creates a View object. + /// + /// Specifies a SQL query string for querying the database. + /// A view object is returned if the query was successful. + public View OpenView(string query) + { + return new View(this, query); + } + + /// + /// Prepares and executes a database query and creates a View object. + /// + /// Specifies a SQL query string for querying the database. + /// A view object is returned if the query was successful. + public View OpenExecuteView(string query) + { + View view = new View(this, query); + + view.Execute(); + return view; + } + + /// + /// Verifies the existence or absence of a table. + /// + /// Table name to to verify the existence of. + /// Returns true if the table exists, false if it does not. + public bool TableExists(string tableName) + { + int result = MsiInterop.MsiDatabaseIsTablePersistent(this.Handle, tableName); + return MsiInterop.MSICONDITIONTRUE == result; + } + + /// + /// Returns a Record containing the names of all the primary + /// key columns for a specified table. + /// + /// Specifies the name of the table from which to obtain + /// primary key names. + /// Returns a Record containing the names of all the + /// primary key columns for a specified table. + public Record PrimaryKeys(string tableName) + { + uint recordHandle; + int error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out recordHandle); + if (0 != error) + { + throw new MsiException(error); + } + + return new Record(recordHandle); + } + + /// + /// Imports a table into the database. + /// + /// Codepage of the database to import table to. + /// Table to import into database. + /// The base directory where intermediate files are created. + /// Whether to keep columns added in a transform. + public void ImportTable(int codepage, Table table, string baseDirectory, bool keepAddedColumns) + { + // write out the table to an IDT file + string idtPath = Path.Combine(baseDirectory, String.Concat(table.Name, ".idt")); + Encoding encoding; + + // If UTF8 encoding, use the UTF8-specific constructor to avoid writing + // the byte order mark at the beginning of the file + if (Encoding.UTF8.CodePage == codepage) + { + encoding = new UTF8Encoding(false, true); + } + else + { + if (0 == codepage) + { + codepage = Encoding.ASCII.CodePage; + } + + encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); + } + + using (StreamWriter idtWriter = new StreamWriter(idtPath, false, encoding)) + { + table.ToIdtDefinition(idtWriter, keepAddedColumns); + } + + // try to import the table into the MSI + try + { + this.Import(idtPath); + } + catch (WixInvalidIdtException) + { + table.ValidateRows(); + + // If ValidateRows finds anything it doesn't like, it throws. Otherwise, we'll + // throw WixInvalidIdtException here which is caught in light and turns off tidy. + throw new WixInvalidIdtException(idtPath, table.Name); + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/Installer.cs b/src/WixToolset.Core.WindowsInstaller/Msi/Installer.cs new file mode 100644 index 00000000..f8bce602 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/Installer.cs @@ -0,0 +1,363 @@ +// 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.Msi +{ + using System; + using System.Diagnostics; + using System.Text; + using WixToolset.Core.Native; + + /// + /// Windows Installer message types. + /// + [Flags] + internal enum InstallMessage + { + /// + /// Premature termination, possibly fatal out of memory. + /// + FatalExit = 0x00000000, + + /// + /// Formatted error message, [1] is message number in Error table. + /// + Error = 0x01000000, + + /// + /// Formatted warning message, [1] is message number in Error table. + /// + Warning = 0x02000000, + + /// + /// User request message, [1] is message number in Error table. + /// + User = 0x03000000, + + /// + /// Informative message for log, not to be displayed. + /// + Info = 0x04000000, + + /// + /// List of files in use that need to be replaced. + /// + FilesInUse = 0x05000000, + + /// + /// Request to determine a valid source location. + /// + ResolveSource = 0x06000000, + + /// + /// Insufficient disk space message. + /// + OutOfDiskSpace = 0x07000000, + + /// + /// Progress: start of action, [1] action name, [2] description, [3] template for ACTIONDATA messages. + /// + ActionStart = 0x08000000, + + /// + /// Action data. Record fields correspond to the template of ACTIONSTART message. + /// + ActionData = 0x09000000, + + /// + /// Progress bar information. See the description of record fields below. + /// + Progress = 0x0A000000, + + /// + /// To enable the Cancel button set [1] to 2 and [2] to 1. To disable the Cancel button set [1] to 2 and [2] to 0. + /// + CommonData = 0x0B000000, + + /// + /// Sent prior to UI initialization, no string data. + /// + Initilize = 0x0C000000, + + /// + /// Sent after UI termination, no string data. + /// + Terminate = 0x0D000000, + + /// + /// Sent prior to display or authored dialog or wizard. + /// + ShowDialog = 0x0E000000 + } + + /// + /// Windows Installer log modes. + /// + [Flags] + internal enum InstallLogModes + { + /// + /// Premature termination of installation. + /// + FatalExit = (1 << ((int)InstallMessage.FatalExit >> 24)), + + /// + /// The error messages are logged. + /// + Error = (1 << ((int)InstallMessage.Error >> 24)), + + /// + /// The warning messages are logged. + /// + Warning = (1 << ((int)InstallMessage.Warning >> 24)), + + /// + /// The user requests are logged. + /// + User = (1 << ((int)InstallMessage.User >> 24)), + + /// + /// The status messages that are not displayed are logged. + /// + Info = (1 << ((int)InstallMessage.Info >> 24)), + + /// + /// Request to determine a valid source location. + /// + ResolveSource = (1 << ((int)InstallMessage.ResolveSource >> 24)), + + /// + /// The was insufficient disk space. + /// + OutOfDiskSpace = (1 << ((int)InstallMessage.OutOfDiskSpace >> 24)), + + /// + /// The start of new installation actions are logged. + /// + ActionStart = (1 << ((int)InstallMessage.ActionStart >> 24)), + + /// + /// The data record with the installation action is logged. + /// + ActionData = (1 << ((int)InstallMessage.ActionData >> 24)), + + /// + /// The parameters for user-interface initialization are logged. + /// + CommonData = (1 << ((int)InstallMessage.CommonData >> 24)), + + /// + /// Logs the property values at termination. + /// + PropertyDump = (1 << ((int)InstallMessage.Progress >> 24)), + + /// + /// Sends large amounts of information to a log file not generally useful to users. + /// May be used for technical support. + /// + Verbose = (1 << ((int)InstallMessage.Initilize >> 24)), + + /// + /// Sends extra debugging information, such as handle creation information, to the log file. + /// + ExtraDebug = (1 << ((int)InstallMessage.Terminate >> 24)), + + /// + /// Progress bar information. This message includes information on units so far and total number of units. + /// See MsiProcessMessage for an explanation of the message format. + /// This message is only sent to an external user interface and is not logged. + /// + Progress = (1 << ((int)InstallMessage.Progress >> 24)), + + /// + /// If this is not a quiet installation, then the basic UI has been initialized. + /// If this is a full UI installation, the full UI is not yet initialized. + /// This message is only sent to an external user interface and is not logged. + /// + Initialize = (1 << ((int)InstallMessage.Initilize >> 24)), + + /// + /// If a full UI is being used, the full UI has ended. + /// If this is not a quiet installation, the basic UI has not yet ended. + /// This message is only sent to an external user interface and is not logged. + /// + Terminate = (1 << ((int)InstallMessage.Terminate >> 24)), + + /// + /// Sent prior to display of the full UI dialog. + /// This message is only sent to an external user interface and is not logged. + /// + ShowDialog = (1 << ((int)InstallMessage.ShowDialog >> 24)), + + /// + /// Files in use information. When this message is received, a FilesInUse Dialog should be displayed. + /// + FilesInUse = (1 << ((int)InstallMessage.FilesInUse >> 24)) + } + + /// + /// Windows Installer UI levels. + /// + [Flags] + internal enum InstallUILevels + { + /// + /// No change in the UI level. However, if phWnd is not Null, the parent window can change. + /// + NoChange = 0, + + /// + /// The installer chooses an appropriate user interface level. + /// + Default = 1, + + /// + /// Completely silent installation. + /// + None = 2, + + /// + /// Simple progress and error handling. + /// + Basic = 3, + + /// + /// Authored user interface with wizard dialog boxes suppressed. + /// + Reduced = 4, + + /// + /// Authored user interface with wizards, progress, and errors. + /// + Full = 5, + + /// + /// If combined with the Basic value, the installer shows simple progress dialog boxes but + /// does not display a Cancel button on the dialog. This prevents users from canceling the install. + /// Available with Windows Installer version 2.0. + /// + HideCancel = 0x20, + + /// + /// If combined with the Basic value, the installer shows simple progress + /// dialog boxes but does not display any modal dialog boxes or error dialog boxes. + /// + ProgressOnly = 0x40, + + /// + /// If combined with any above value, the installer displays a modal dialog + /// box at the end of a successful installation or if there has been an error. + /// No dialog box is displayed if the user cancels. + /// + EndDialog = 0x80, + + /// + /// If this value is combined with the None value, the installer displays only the dialog + /// boxes used for source resolution. No other dialog boxes are shown. This value has no + /// effect if the UI level is not INSTALLUILEVEL_NONE. It is used with an external user + /// interface designed to handle all of the UI except for source resolution. In this case, + /// the installer handles source resolution. This value is only available with Windows Installer 2.0 and later. + /// + SourceResOnly = 0x100 + } + + /// + /// Represents the Windows Installer, provides wrappers to + /// create the top-level objects and access their methods. + /// + internal sealed class Installer + { + /// + /// Protect the constructor. + /// + private Installer() + { + } + + /// + /// Takes the path to a file and returns a 128-bit hash of that file. + /// + /// Path to file that is to be hashed. + /// The value in this column must be 0. This parameter is reserved for future use. + /// Int array that receives the returned file hash information. + internal static void GetFileHash(string filePath, int options, out int[] hash) + { + MsiInterop.MSIFILEHASHINFO hashInterop = new MsiInterop.MSIFILEHASHINFO(); + hashInterop.FileHashInfoSize = 20; + + int error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop); + if (0 != error) + { + throw new MsiException(error); + } + + Debug.Assert(20 == hashInterop.FileHashInfoSize); + + hash = new int[4]; + hash[0] = hashInterop.Data0; + hash[1] = hashInterop.Data1; + hash[2] = hashInterop.Data2; + hash[3] = hashInterop.Data3; + } + + /// + /// Returns the version string and language string in the format that the installer + /// expects to find them in the database. If you just want version information, set + /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set + /// lpVersionBuf and pcchVersionBuf to zero. + /// + /// Specifies the path to the file. + /// Returns the file version. Set to 0 for language information only. + /// Returns the file language. Set to 0 for version information only. + internal static void GetFileVersion(string filePath, out string version, out string language) + { + int versionLength = 20; + int languageLength = 20; + StringBuilder versionBuffer = new StringBuilder(versionLength); + StringBuilder languageBuffer = new StringBuilder(languageLength); + + int error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); + if (234 == error) + { + versionBuffer.EnsureCapacity(++versionLength); + languageBuffer.EnsureCapacity(++languageLength); + error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); + } + else if (1006 == error) + { + // file has no version or language, so no error + error = 0; + } + + if (0 != error) + { + throw new MsiException(error); + } + + version = versionBuffer.ToString(); + language = languageBuffer.ToString(); + } + + /// + /// Enables an external user-interface handler. + /// + /// Specifies a callback function. + /// Specifies which messages to handle using the external message handler. + /// Pointer to an application context that is passed to the callback function. + /// The return value is the previously set external handler, or null if there was no previously set handler. + internal static InstallUIHandler SetExternalUI(InstallUIHandler installUIHandler, int messageFilter, IntPtr context) + { + return MsiInterop.MsiSetExternalUI(installUIHandler, messageFilter, context); + } + + /// + /// Enables the installer's internal user interface. + /// + /// Specifies the level of complexity of the user interface. + /// Pointer to a window. This window becomes the owner of any user interface created. + /// The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned. + internal static int SetInternalUI(int uiLevel, ref IntPtr hwnd) + { + return MsiInterop.MsiSetInternalUI(uiLevel, ref hwnd); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/Interop/MsiInterop.cs b/src/WixToolset.Core.WindowsInstaller/Msi/Interop/MsiInterop.cs new file mode 100644 index 00000000..054289ee --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/Interop/MsiInterop.cs @@ -0,0 +1,697 @@ +// 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 false +namespace WixToolset.Msi.Interop +{ + using System; + using System.Text; + using System.Runtime.InteropServices; + using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + + /// + /// A callback function that the installer calls for progress notification and error messages. + /// + /// Pointer to an application context. + /// This parameter can be used for error checking. + /// Specifies a combination of one message box style, + /// one message box icon type, one default button, and one installation message type. + /// Specifies the message text. + /// -1 for an error, 0 if no action was taken, 1 if OK, 3 to abort. + internal delegate int InstallUIHandler(IntPtr context, uint messageType, [MarshalAs(UnmanagedType.LPWStr)] string message); + + /// + /// Class exposing static functions and structs from MSI API. + /// + internal sealed class MsiInterop + { + // Patching constants + internal const int MsiMaxStreamNameLength = 62; // http://msdn2.microsoft.com/library/aa370551.aspx + + // Component.Attributes + internal const int MsidbComponentAttributesLocalOnly = 0; + internal const int MsidbComponentAttributesSourceOnly = 1; + internal const int MsidbComponentAttributesOptional = 2; + internal const int MsidbComponentAttributesRegistryKeyPath = 4; + internal const int MsidbComponentAttributesSharedDllRefCount = 8; + internal const int MsidbComponentAttributesPermanent = 16; + internal const int MsidbComponentAttributesODBCDataSource = 32; + internal const int MsidbComponentAttributesTransitive = 64; + internal const int MsidbComponentAttributesNeverOverwrite = 128; + internal const int MsidbComponentAttributes64bit = 256; + internal const int MsidbComponentAttributesDisableRegistryReflection = 512; + internal const int MsidbComponentAttributesUninstallOnSupersedence = 1024; + internal const int MsidbComponentAttributesShared = 2048; + + // BBControl.Attributes & Control.Attributes + internal const int MsidbControlAttributesVisible = 0x00000001; + internal const int MsidbControlAttributesEnabled = 0x00000002; + internal const int MsidbControlAttributesSunken = 0x00000004; + internal const int MsidbControlAttributesIndirect = 0x00000008; + internal const int MsidbControlAttributesInteger = 0x00000010; + internal const int MsidbControlAttributesRTLRO = 0x00000020; + internal const int MsidbControlAttributesRightAligned = 0x00000040; + internal const int MsidbControlAttributesLeftScroll = 0x00000080; + internal const int MsidbControlAttributesBiDi = MsidbControlAttributesRTLRO | MsidbControlAttributesRightAligned | MsidbControlAttributesLeftScroll; + + // Text controls + internal const int MsidbControlAttributesTransparent = 0x00010000; + internal const int MsidbControlAttributesNoPrefix = 0x00020000; + internal const int MsidbControlAttributesNoWrap = 0x00040000; + internal const int MsidbControlAttributesFormatSize = 0x00080000; + internal const int MsidbControlAttributesUsersLanguage = 0x00100000; + + // Edit controls + internal const int MsidbControlAttributesMultiline = 0x00010000; + internal const int MsidbControlAttributesPasswordInput = 0x00200000; + + // ProgressBar controls + internal const int MsidbControlAttributesProgress95 = 0x00010000; + + // VolumeSelectCombo and DirectoryCombo controls + internal const int MsidbControlAttributesRemovableVolume = 0x00010000; + internal const int MsidbControlAttributesFixedVolume = 0x00020000; + internal const int MsidbControlAttributesRemoteVolume = 0x00040000; + internal const int MsidbControlAttributesCDROMVolume = 0x00080000; + internal const int MsidbControlAttributesRAMDiskVolume = 0x00100000; + internal const int MsidbControlAttributesFloppyVolume = 0x00200000; + + // VolumeCostList controls + internal const int MsidbControlShowRollbackCost = 0x00400000; + + // ListBox and ComboBox controls + internal const int MsidbControlAttributesSorted = 0x00010000; + internal const int MsidbControlAttributesComboList = 0x00020000; + + // picture button controls + internal const int MsidbControlAttributesImageHandle = 0x00010000; + internal const int MsidbControlAttributesPushLike = 0x00020000; + internal const int MsidbControlAttributesBitmap = 0x00040000; + internal const int MsidbControlAttributesIcon = 0x00080000; + internal const int MsidbControlAttributesFixedSize = 0x00100000; + internal const int MsidbControlAttributesIconSize16 = 0x00200000; + internal const int MsidbControlAttributesIconSize32 = 0x00400000; + internal const int MsidbControlAttributesIconSize48 = 0x00600000; + internal const int MsidbControlAttributesElevationShield = 0x00800000; + + // RadioButton controls + internal const int MsidbControlAttributesHasBorder = 0x01000000; + + // CustomAction.Type + // executable types + internal const int MsidbCustomActionTypeDll = 0x00000001; // Target = entry point name + internal const int MsidbCustomActionTypeExe = 0x00000002; // Target = command line args + internal const int MsidbCustomActionTypeTextData = 0x00000003; // Target = text string to be formatted and set into property + internal const int MsidbCustomActionTypeJScript = 0x00000005; // Target = entry point name; null if none to call + internal const int MsidbCustomActionTypeVBScript = 0x00000006; // Target = entry point name; null if none to call + internal const int MsidbCustomActionTypeInstall = 0x00000007; // Target = property list for nested engine initialization + internal const int MsidbCustomActionTypeSourceBits = 0x00000030; + internal const int MsidbCustomActionTypeTargetBits = 0x00000007; + internal const int MsidbCustomActionTypeReturnBits = 0x000000C0; + internal const int MsidbCustomActionTypeExecuteBits = 0x00000700; + + // source of code + internal const int MsidbCustomActionTypeBinaryData = 0x00000000; // Source = Binary.Name; data stored in stream + internal const int MsidbCustomActionTypeSourceFile = 0x00000010; // Source = File.File; file part of installation + internal const int MsidbCustomActionTypeDirectory = 0x00000020; // Source = Directory.Directory; folder containing existing file + internal const int MsidbCustomActionTypeProperty = 0x00000030; // Source = Property.Property; full path to executable + + // return processing; default is syncronous execution; process return code + internal const int MsidbCustomActionTypeContinue = 0x00000040; // ignore action return status; continue running + internal const int MsidbCustomActionTypeAsync = 0x00000080; // run asynchronously + + // execution scheduling flags; default is execute whenever sequenced + internal const int MsidbCustomActionTypeFirstSequence = 0x00000100; // skip if UI sequence already run + internal const int MsidbCustomActionTypeOncePerProcess = 0x00000200; // skip if UI sequence already run in same process + internal const int MsidbCustomActionTypeClientRepeat = 0x00000300; // run on client only if UI already run on client + internal const int MsidbCustomActionTypeInScript = 0x00000400; // queue for execution within script + internal const int MsidbCustomActionTypeRollback = 0x00000100; // in conjunction with InScript: queue in Rollback script + internal const int MsidbCustomActionTypeCommit = 0x00000200; // in conjunction with InScript: run Commit ops from script on success + + // security context flag; default to impersonate as user; valid only if InScript + internal const int MsidbCustomActionTypeNoImpersonate = 0x00000800; // no impersonation; run in system context + internal const int MsidbCustomActionTypeTSAware = 0x00004000; // impersonate for per-machine installs on TS machines + internal const int MsidbCustomActionType64BitScript = 0x00001000; // script should run in 64bit process + internal const int MsidbCustomActionTypeHideTarget = 0x00002000; // don't record the contents of the Target field in the log file. + + internal const int MsidbCustomActionTypePatchUninstall = 0x00008000; // run on patch uninstall + + // Dialog.Attributes + internal const int MsidbDialogAttributesVisible = 0x00000001; + internal const int MsidbDialogAttributesModal = 0x00000002; + internal const int MsidbDialogAttributesMinimize = 0x00000004; + internal const int MsidbDialogAttributesSysModal = 0x00000008; + internal const int MsidbDialogAttributesKeepModeless = 0x00000010; + internal const int MsidbDialogAttributesTrackDiskSpace = 0x00000020; + internal const int MsidbDialogAttributesUseCustomPalette = 0x00000040; + internal const int MsidbDialogAttributesRTLRO = 0x00000080; + internal const int MsidbDialogAttributesRightAligned = 0x00000100; + internal const int MsidbDialogAttributesLeftScroll = 0x00000200; + internal const int MsidbDialogAttributesBiDi = MsidbDialogAttributesRTLRO | MsidbDialogAttributesRightAligned | MsidbDialogAttributesLeftScroll; + internal const int MsidbDialogAttributesError = 0x00010000; + internal const int CommonControlAttributesInvert = MsidbControlAttributesVisible + MsidbControlAttributesEnabled; + internal const int DialogAttributesInvert = MsidbDialogAttributesVisible + MsidbDialogAttributesModal + MsidbDialogAttributesMinimize; + + // Feature.Attributes + internal const int MsidbFeatureAttributesFavorLocal = 0; + internal const int MsidbFeatureAttributesFavorSource = 1; + internal const int MsidbFeatureAttributesFollowParent = 2; + internal const int MsidbFeatureAttributesFavorAdvertise = 4; + internal const int MsidbFeatureAttributesDisallowAdvertise = 8; + internal const int MsidbFeatureAttributesUIDisallowAbsent = 16; + internal const int MsidbFeatureAttributesNoUnsupportedAdvertise = 32; + + // File.Attributes + internal const int MsidbFileAttributesReadOnly = 1; + internal const int MsidbFileAttributesHidden = 2; + internal const int MsidbFileAttributesSystem = 4; + internal const int MsidbFileAttributesVital = 512; + internal const int MsidbFileAttributesChecksum = 1024; + internal const int MsidbFileAttributesPatchAdded = 4096; + internal const int MsidbFileAttributesNoncompressed = 8192; + internal const int MsidbFileAttributesCompressed = 16384; + + // IniFile.Action & RemoveIniFile.Action + internal const int MsidbIniFileActionAddLine = 0; + internal const int MsidbIniFileActionCreateLine = 1; + internal const int MsidbIniFileActionRemoveLine = 2; + internal const int MsidbIniFileActionAddTag = 3; + internal const int MsidbIniFileActionRemoveTag = 4; + + // MoveFile.Options + internal const int MsidbMoveFileOptionsMove = 1; + + // ServiceInstall.Attributes + internal const int MsidbServiceInstallOwnProcess = 0x00000010; + internal const int MsidbServiceInstallShareProcess = 0x00000020; + internal const int MsidbServiceInstallInteractive = 0x00000100; + internal const int MsidbServiceInstallAutoStart = 0x00000002; + internal const int MsidbServiceInstallDemandStart = 0x00000003; + internal const int MsidbServiceInstallDisabled = 0x00000004; + internal const int MsidbServiceInstallErrorIgnore = 0x00000000; + internal const int MsidbServiceInstallErrorNormal = 0x00000001; + internal const int MsidbServiceInstallErrorCritical = 0x00000003; + internal const int MsidbServiceInstallErrorControlVital = 0x00008000; + + // ServiceConfig.Event + internal const int MsidbServiceConfigEventInstall = 0x00000001; + internal const int MsidbServiceConfigEventUninstall = 0x00000002; + internal const int MsidbServiceConfigEventReinstall = 0x00000004; + + // ServiceControl.Attributes + internal const int MsidbServiceControlEventStart = 0x00000001; + internal const int MsidbServiceControlEventStop = 0x00000002; + internal const int MsidbServiceControlEventDelete = 0x00000008; + internal const int MsidbServiceControlEventUninstallStart = 0x00000010; + internal const int MsidbServiceControlEventUninstallStop = 0x00000020; + internal const int MsidbServiceControlEventUninstallDelete = 0x00000080; + + // TextStyle.StyleBits + internal const int MsidbTextStyleStyleBitsBold = 1; + internal const int MsidbTextStyleStyleBitsItalic = 2; + internal const int MsidbTextStyleStyleBitsUnderline = 4; + internal const int MsidbTextStyleStyleBitsStrike = 8; + + // Upgrade.Attributes + internal const int MsidbUpgradeAttributesMigrateFeatures = 0x00000001; + internal const int MsidbUpgradeAttributesOnlyDetect = 0x00000002; + internal const int MsidbUpgradeAttributesIgnoreRemoveFailure = 0x00000004; + internal const int MsidbUpgradeAttributesVersionMinInclusive = 0x00000100; + internal const int MsidbUpgradeAttributesVersionMaxInclusive = 0x00000200; + internal const int MsidbUpgradeAttributesLanguagesExclusive = 0x00000400; + + // Registry Hive Roots + internal const int MsidbRegistryRootClassesRoot = 0; + internal const int MsidbRegistryRootCurrentUser = 1; + internal const int MsidbRegistryRootLocalMachine = 2; + internal const int MsidbRegistryRootUsers = 3; + + // Locator Types + internal const int MsidbLocatorTypeDirectory = 0; + internal const int MsidbLocatorTypeFileName = 1; + internal const int MsidbLocatorTypeRawValue = 2; + internal const int MsidbLocatorType64bit = 16; + + internal const int MsidbClassAttributesRelativePath = 1; + + // RemoveFile.InstallMode + internal const int MsidbRemoveFileInstallModeOnInstall = 0x00000001; + internal const int MsidbRemoveFileInstallModeOnRemove = 0x00000002; + internal const int MsidbRemoveFileInstallModeOnBoth = 0x00000003; + + // ODBCDataSource.Registration + internal const int MsidbODBCDataSourceRegistrationPerMachine = 0; + internal const int MsidbODBCDataSourceRegistrationPerUser = 1; + + // ModuleConfiguration.Format + internal const int MsidbModuleConfigurationFormatText = 0; + internal const int MsidbModuleConfigurationFormatKey = 1; + internal const int MsidbModuleConfigurationFormatInteger = 2; + internal const int MsidbModuleConfigurationFormatBitfield = 3; + + // ModuleConfiguration.Attributes + internal const int MsidbMsmConfigurableOptionKeyNoOrphan = 1; + internal const int MsidbMsmConfigurableOptionNonNullable = 2; + + // ' Windows API function ShowWindow constants - used in Shortcut table + internal const int SWSHOWNORMAL = 0x00000001; + internal const int SWSHOWMAXIMIZED = 0x00000003; + internal const int SWSHOWMINNOACTIVE = 0x00000007; + + // NameToBit arrays + // UI elements + internal static readonly string[] CommonControlAttributes = { "Hidden", "Disabled", "Sunken", "Indirect", "Integer", "RightToLeft", "RightAligned", "LeftScroll" }; + internal static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; + internal static readonly string[] HyperlinkControlAttributes = { "Transparent" }; + internal static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; + internal static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; + internal static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; + internal static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; + internal static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; + internal static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; + internal static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; + internal static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; + internal static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; + internal static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; + internal static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; + + internal const int MsidbEmbeddedUI = 0x01; + internal const int MsidbEmbeddedHandlesBasic = 0x02; + + internal const int INSTALLLOGMODE_FATALEXIT = 0x00001; + internal const int INSTALLLOGMODE_ERROR = 0x00002; + internal const int INSTALLLOGMODE_WARNING = 0x00004; + internal const int INSTALLLOGMODE_USER = 0x00008; + internal const int INSTALLLOGMODE_INFO = 0x00010; + internal const int INSTALLLOGMODE_FILESINUSE = 0x00020; + internal const int INSTALLLOGMODE_RESOLVESOURCE = 0x00040; + internal const int INSTALLLOGMODE_OUTOFDISKSPACE = 0x00080; + internal const int INSTALLLOGMODE_ACTIONSTART = 0x00100; + internal const int INSTALLLOGMODE_ACTIONDATA = 0x00200; + internal const int INSTALLLOGMODE_PROGRESS = 0x00400; + internal const int INSTALLLOGMODE_COMMONDATA = 0x00800; + internal const int INSTALLLOGMODE_INITIALIZE = 0x01000; + internal const int INSTALLLOGMODE_TERMINATE = 0x02000; + internal const int INSTALLLOGMODE_SHOWDIALOG = 0x04000; + internal const int INSTALLLOGMODE_RMFILESINUSE = 0x02000000; + internal const int INSTALLLOGMODE_INSTALLSTART = 0x04000000; + internal const int INSTALLLOGMODE_INSTALLEND = 0x08000000; + + internal const int MSICONDITIONFALSE = 0; // The table is temporary. + internal const int MSICONDITIONTRUE = 1; // The table is persistent. + internal const int MSICONDITIONNONE = 2; // The table is unknown. + internal const int MSICONDITIONERROR = 3; // An invalid handle or invalid parameter was passed to the function. + + internal const int MSIDBOPENREADONLY = 0; + internal const int MSIDBOPENTRANSACT = 1; + internal const int MSIDBOPENDIRECT = 2; + internal const int MSIDBOPENCREATE = 3; + internal const int MSIDBOPENCREATEDIRECT = 4; + internal const int MSIDBOPENPATCHFILE = 32; + + internal const int MSIMODIFYSEEK = -1; // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks. + internal const int MSIMODIFYREFRESH = 0; // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records. + internal const int MSIMODIFYINSERT = 1; // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYUPDATE = 2; // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records. + internal const int MSIMODIFYASSIGN = 3; // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYREPLACE = 4; // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYMERGE = 5; // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYDELETE = 6; // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYINSERTTEMPORARY = 7; // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYVALIDATE = 8; // Validates a record. Does not validate across joins. You must first call the MsiViewFetch function with the same record. Obtain validation errors with MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYVALIDATENEW = 9; // Validate a new record. Does not validate across joins. Checks for duplicate keys. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYVALIDATEFIELD = 10; // Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. + internal const int MSIMODIFYVALIDATEDELETE = 11; // Validates a record that will be deleted later. You must first call MsiViewFetch. Fails if another row refers to the primary keys of this row. Validation does not check for the existence of the primary keys of this row in properties or strings. Does not check if a column is a foreign key to multiple tables. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. + + internal const uint VTI2 = 2; + internal const uint VTI4 = 3; + internal const uint VTLPWSTR = 30; + internal const uint VTFILETIME = 64; + + internal const int MSICOLINFONAMES = 0; // return column names + internal const int MSICOLINFOTYPES = 1; // return column definitions, datatype code followed by width + + /// + /// Protect the constructor. + /// + private MsiInterop() + { + } + + /// + /// PInvoke of MsiCloseHandle. + /// + /// Handle to a database. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiCloseHandle", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiCloseHandle(uint database); + + /// + /// PInvoke of MsiCreateRecord + /// + /// Count of columns in the record. + /// Handle referencing the record. + [DllImport("msi.dll", EntryPoint = "MsiCreateRecord", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern uint MsiCreateRecord(int parameters); + + /// + /// Creates summary information of an existing transform to include validation and error conditions. + /// + /// The handle to the database that contains the new database summary information. + /// The handle to the database that contains the original summary information. + /// The name of the transform to which the summary information is added. + /// The error conditions that should be suppressed when the transform is applied. + /// Specifies the properties to be validated to verify that the transform can be applied to the database. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiCreateTransformSummaryInfoW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiCreateTransformSummaryInfo(uint database, uint referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations); + + /// + /// Applies a transform to a database. + /// + /// Handle to the database obtained from MsiOpenDatabase to transform. + /// Specifies the name of the transform file to apply. + /// Error conditions that should be suppressed. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseApplyTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseApplyTransform(uint database, string transformFile, TransformErrorConditions errorConditions); + + /// + /// PInvoke of MsiDatabaseCommit. + /// + /// Handle to a databse. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseCommit", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseCommit(uint database); + + /// + /// PInvoke of MsiDatabaseExportW. + /// + /// Handle to a database. + /// Table name. + /// Folder path. + /// File name. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseExportW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseExport(uint database, string tableName, string folderPath, string fileName); + + /// + /// Generates a transform file of differences between two databases. + /// + /// Handle to the database obtained from MsiOpenDatabase that includes the changes. + /// Handle to the database obtained from MsiOpenDatabase that does not include the changes. + /// A null-terminated string that specifies the name of the transform file being generated. + /// This parameter can be null. If szTransformFile is null, you can use MsiDatabaseGenerateTransform to test whether two + /// databases are identical without creating a transform. If the databases are identical, the function returns ERROR_NO_DATA. + /// If the databases are different the function returns NOERROR. + /// This is a reserved argument and must be set to 0. + /// This is a reserved argument and must be set to 0. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseGenerateTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseGenerateTransform(uint database, uint databaseReference, string transformFile, int reserved1, int reserved2); + + /// + /// PInvoke of MsiDatabaseImportW. + /// + /// Handle to a database. + /// Folder path. + /// File name. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseImportW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseImport(uint database, string folderPath, string fileName); + + /// + /// PInvoke of MsiDatabaseMergeW. + /// + /// The handle to the database obtained from MsiOpenDatabase. + /// The handle to the database obtained from MsiOpenDatabase to merge into the base database. + /// The name of the table to receive merge conflict information. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseMergeW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseMerge(uint database, uint databaseMerge, string tableName); + + /// + /// PInvoke of MsiDatabaseOpenViewW. + /// + /// Handle to a database. + /// SQL query. + /// View handle. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseOpenViewW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseOpenView(uint database, string query, out uint view); + + /// + /// PInvoke of MsiGetFileHashW. + /// + /// File path. + /// Hash options (must be 0). + /// Buffer to recieve hash. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiGetFileHashW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiGetFileHash(string filePath, uint options, MSIFILEHASHINFO hash); + + /// + /// PInvoke of MsiGetFileVersionW. + /// + /// File path. + /// Buffer to receive version info. + /// Size of version buffer. + /// Buffer to recieve lang info. + /// Size of lang buffer. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiGetFileVersionW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiGetFileVersion(string filePath, StringBuilder versionBuf, ref int versionBufSize, StringBuilder langBuf, ref int langBufSize); + + /// + /// PInvoke of MsiGetLastErrorRecord. + /// + /// Handle to error record if one exists. + [DllImport("msi.dll", EntryPoint = "MsiGetLastErrorRecord", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern uint MsiGetLastErrorRecord(); + + /// + /// PInvoke of MsiDatabaseGetPrimaryKeysW. + /// + /// Handle to a database. + /// Table name. + /// Handle to receive resulting record. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDatabaseGetPrimaryKeysW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseGetPrimaryKeys(uint database, string tableName, out uint record); + + /// + /// PInvoke of MsiDoActionW. + /// + /// Handle to the installation provided to a DLL custom action or + /// obtained through MsiOpenPackage, MsiOpenPackageEx, or MsiOpenProduct. + /// Specifies the action to execute. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiDoActionW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDoAction(uint product, string action); + + /// + /// PInvoke of MsiGetSummaryInformationW. Can use either database handle or database path as input. + /// + /// Handle to a database. + /// Path to a database. + /// Max number of updated values. + /// Handle to summary information. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiGetSummaryInformationW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiGetSummaryInformation(uint database, string databasePath, uint updateCount, ref uint summaryInfo); + + /// + /// PInvoke of MsiDatabaseIsTablePersitentW. + /// + /// Handle to a database. + /// Table name. + /// MSICONDITION + [DllImport("msi.dll", EntryPoint = "MsiDatabaseIsTablePersistentW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiDatabaseIsTablePersistent(uint database, string tableName); + + /// + /// PInvoke of MsiOpenDatabaseW. + /// + /// Path to database. + /// Persist mode. + /// Handle to database. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiOpenDatabaseW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiOpenDatabase(string databasePath, IntPtr persist, out uint database); + + /// + /// PInvoke of MsiOpenPackageW. + /// + /// The path to the package. + /// A pointer to a variable that receives the product handle. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiOpenPackageW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiOpenPackage(string packagePath, out uint product); + + /// + /// PInvoke of MsiRecordIsNull. + /// + /// MSI Record handle. + /// Index of field to check for null value. + /// true if the field is null, false if not, and an error code for any error. + [DllImport("msi.dll", EntryPoint = "MsiRecordIsNull", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordIsNull(uint record, int field); + + /// + /// PInvoke of MsiRecordGetInteger. + /// + /// MSI Record handle. + /// Index of field to retrieve integer from. + /// Integer value. + [DllImport("msi.dll", EntryPoint = "MsiRecordGetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordGetInteger(uint record, int field); + + /// + /// PInvoke of MsiRectordSetInteger. + /// + /// MSI Record handle. + /// Index of field to set integer value in. + /// Value to set field to. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiRecordSetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordSetInteger(uint record, int field, int value); + + /// + /// PInvoke of MsiRecordGetStringW. + /// + /// MSI Record handle. + /// Index of field to get string value from. + /// Buffer to recieve value. + /// Size of buffer. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiRecordGetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordGetString(uint record, int field, StringBuilder valueBuf, ref int valueBufSize); + + /// + /// PInvoke of MsiRecordSetStringW. + /// + /// MSI Record handle. + /// Index of field to set string value in. + /// String value. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiRecordSetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordSetString(uint record, int field, string value); + + /// + /// PInvoke of MsiRecordSetStreamW. + /// + /// MSI Record handle. + /// Index of field to set stream value in. + /// Path to file to set stream value to. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiRecordSetStreamW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordSetStream(uint record, int field, string filePath); + + /// + /// PInvoke of MsiRecordReadStreamW. + /// + /// MSI Record handle. + /// Index of field to read stream from. + /// Data buffer to recieve stream value. + /// Size of data buffer. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiRecordReadStream", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordReadStream(uint record, int field, byte[] dataBuf, ref int dataBufSize); + + /// + /// PInvoke of MsiRecordGetFieldCount. + /// + /// MSI Record handle. + /// Count of fields in the record. + [DllImport("msi.dll", EntryPoint = "MsiRecordGetFieldCount", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiRecordGetFieldCount(uint record); + + /// + /// PInvoke of MsiSetExternalUIW. + /// + /// Specifies a callback function that conforms to the INSTALLUI_HANDLER specification. + /// Specifies which messages to handle using the external message handler. If the external + /// handler returns a non-zero result, then that message will not be sent to the UI, instead the message will be logged + /// if logging has been enabled. + /// Pointer to an application context that is passed to the callback function. + /// This parameter can be used for error checking. + /// The return value is the previously set external handler, or zero (0) if there was no previously set handler. + [DllImport("msi.dll", EntryPoint = "MsiSetExternalUIW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern InstallUIHandler MsiSetExternalUI(InstallUIHandler installUIHandler, int installLogMode, IntPtr context); + + /// + /// PInvoke of MsiSetInternalUI. + /// + /// Specifies the level of complexity of the user interface. + /// Pointer to a window. This window becomes the owner of any user interface created. + /// A pointer to the previous owner of the user interface is returned. + /// If this parameter is null, the owner of the user interface does not change. + /// The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned. + [DllImport("msi.dll", EntryPoint = "MsiSetInternalUI", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiSetInternalUI(int uiLevel, ref IntPtr hwnd); + + /// + /// PInvoke of MsiSummaryInfoGetPropertyW. + /// + /// Handle to summary info. + /// Property to get value from. + /// Data type of property. + /// Integer to receive integer value. + /// File time to receive file time value. + /// String buffer to receive string value. + /// Size of string buffer. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiSummaryInfoGetPropertyW", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiSummaryInfoGetProperty(uint summaryInfo, int property, out uint dataType, out int integerValue, ref FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize); + + /// + /// PInvoke of MsiViewGetColumnInfo. + /// + /// Handle to view. + /// Column info. + /// Handle for returned record. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiViewGetColumnInfo", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiViewGetColumnInfo(uint view, int columnInfo, out uint record); + + /// + /// PInvoke of MsiViewExecute. + /// + /// Handle of view to execute. + /// Handle to a record that supplies the parameters for the view. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiViewExecute", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiViewExecute(uint view, uint record); + + /// + /// PInvoke of MsiViewFetch. + /// + /// Handle of view to fetch a row from. + /// Handle to receive record info. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiViewFetch", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiViewFetch(uint view, out uint record); + + /// + /// PInvoke of MsiViewModify. + /// + /// Handle of view to modify. + /// Modify mode. + /// Handle of record. + /// Error code. + [DllImport("msi.dll", EntryPoint = "MsiViewModify", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int MsiViewModify(uint view, int modifyMode, uint record); + + /// + /// contains the file hash information returned by MsiGetFileHash and used in the MsiFileHash table. + /// + [StructLayout(LayoutKind.Explicit)] + internal class MSIFILEHASHINFO + { + [FieldOffset(0)] internal uint FileHashInfoSize; + [FieldOffset(4)] internal int Data0; + [FieldOffset(8)] internal int Data1; + [FieldOffset(12)]internal int Data2; + [FieldOffset(16)]internal int Data3; + } + } +} +#endif \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/MsiException.cs b/src/WixToolset.Core.WindowsInstaller/Msi/MsiException.cs new file mode 100644 index 00000000..b33bf27a --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/MsiException.cs @@ -0,0 +1,78 @@ +// 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.Msi +{ + using System; + using System.ComponentModel; + using WixToolset.Core.Native; + + /// + /// Exception that wraps MsiGetLastError(). + /// + [Serializable] + public class MsiException : Win32Exception + { + /// + /// Instantiate a new MsiException with a given error. + /// + /// The error code from the MsiXxx() function call. + public MsiException(int error) : base(error) + { + uint handle = MsiInterop.MsiGetLastErrorRecord(); + if (0 != handle) + { + using (Record record = new Record(handle)) + { + this.MsiError = record.GetInteger(1); + + int errorInfoCount = record.GetFieldCount() - 1; + this.ErrorInfo = new string[errorInfoCount]; + for (int i = 0; i < errorInfoCount; ++i) + { + this.ErrorInfo[i] = record.GetString(i + 2); + } + } + } + else + { + this.MsiError = 0; + this.ErrorInfo = new string[0]; + } + + this.Error = error; + } + + /// + /// Gets the error number. + /// + public int Error { get; private set; } + + /// + /// Gets the internal MSI error number. + /// + public int MsiError { get; private set; } + + /// + /// Gets any additional the error information. + /// + public string[] ErrorInfo { get; private set; } + + /// + /// Overrides Message property to return useful error message. + /// + public override string Message + { + get + { + if (0 == this.MsiError) + { + return base.Message; + } + else + { + return String.Format("Internal MSI failure. Win32 error: {0}, MSI error: {1}, detail: {2}", this.Error, this.MsiError, String.Join(", ", this.ErrorInfo)); + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/MsiHandle.cs b/src/WixToolset.Core.WindowsInstaller/Msi/MsiHandle.cs new file mode 100644 index 00000000..6d2dc984 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/MsiHandle.cs @@ -0,0 +1,116 @@ +// 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.Msi +{ + using System; + using System.ComponentModel; + using System.Diagnostics; + using System.Threading; + using WixToolset.Core.Native; + + /// + /// Wrapper class for MSI handle. + /// + public class MsiHandle : IDisposable + { + private bool disposed; + private uint handle; + private int owningThread; +#if DEBUG + private string creationStack; +#endif + + /// + /// MSI handle destructor. + /// + ~MsiHandle() + { + this.Dispose(false); + } + + /// + /// Gets or sets the MSI handle. + /// + /// The MSI handle. + internal uint Handle + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("MsiHandle"); + } + + return this.handle; + } + + set + { + if (this.disposed) + { + throw new ObjectDisposedException("MsiHandle"); + } + + this.handle = value; + this.owningThread = Thread.CurrentThread.ManagedThreadId; +#if DEBUG + this.creationStack = Environment.StackTrace; +#endif + } + } + + /// + /// Close the MSI handle. + /// + public void Close() + { + this.Dispose(); + } + + /// + /// Disposes the managed and unmanaged objects in this object. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the managed and unmanaged objects in this object. + /// + /// true to dispose the managed objects. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (0 != this.handle) + { + if (Thread.CurrentThread.ManagedThreadId == this.owningThread) + { + int error = MsiInterop.MsiCloseHandle(this.handle); + if (0 != error) + { + throw new Win32Exception(error); + } + this.handle = 0; + } + else + { + // Don't try to close the handle on a different thread than it was opened. + // This will occasionally cause MSI to AV. + string message = String.Format("Leaked msi handle {0} created on thread {1} by type {2}. This handle cannot be closed on thread {3}", + this.handle, this.owningThread, this.GetType(), Thread.CurrentThread.ManagedThreadId); +#if DEBUG + throw new InvalidOperationException(String.Format("{0}. Created {1}", message, this.creationStack)); +#else + Debug.WriteLine(message); +#endif + } + } + + this.disposed = true; + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/Record.cs b/src/WixToolset.Core.WindowsInstaller/Msi/Record.cs new file mode 100644 index 00000000..438aa3b0 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/Record.cs @@ -0,0 +1,182 @@ +// 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.Msi +{ + using System; + using System.ComponentModel; + using System.Text; + using WixToolset.Core.Native; + + /// + /// Wrapper class around msi.dll interop for a record. + /// + public sealed class Record : MsiHandle + { + /// + /// Creates a record with the specified number of fields. + /// + /// Number of fields in record. + public Record(int fieldCount) + { + this.Handle = MsiInterop.MsiCreateRecord(fieldCount); + if (0 == this.Handle) + { + throw new OutOfMemoryException(); + } + } + + /// + /// Creates a record from a handle. + /// + /// Handle to create record from. + internal Record(uint handle) + { + this.Handle = handle; + } + + /// + /// Gets a string value at specified location. + /// + /// Index into record to get string. + public string this[int field] + { + get { return this.GetString(field); } + set { this.SetString(field, (string)value); } + } + + /// + /// Determines if the value is null at the specified location. + /// + /// Index into record of the field to query. + /// true if the value is null, false otherwise. + public bool IsNull(int field) + { + int error = MsiInterop.MsiRecordIsNull(this.Handle, field); + + switch (error) + { + case 0: + return false; + case 1: + return true; + default: + throw new Win32Exception(error); + } + } + + /// + /// Gets integer value at specified location. + /// + /// Index into record to get integer + /// Integer value + public int GetInteger(int field) + { + return MsiInterop.MsiRecordGetInteger(this.Handle, field); + } + + /// + /// Sets integer value at specified location. + /// + /// Index into record to set integer. + /// Value to set into record. + public void SetInteger(int field, int value) + { + int error = MsiInterop.MsiRecordSetInteger(this.Handle, field, value); + if (0 != error) + { + throw new Win32Exception(error); + } + } + + /// + /// Gets string value at specified location. + /// + /// Index into record to get string. + /// String value + public string GetString(int field) + { + int bufferSize = 255; + StringBuilder buffer = new StringBuilder(bufferSize); + int error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize); + if (234 == error) + { + buffer.EnsureCapacity(++bufferSize); + error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize); + } + + if (0 != error) + { + throw new Win32Exception(error); + } + + return (0 < buffer.Length ? buffer.ToString() : null); + } + + /// + /// Set string value at specified location + /// + /// Index into record to set string. + /// Value to set into record + public void SetString(int field, string value) + { + int error = MsiInterop.MsiRecordSetString(this.Handle, field, value); + if (0 != error) + { + throw new Win32Exception(error); + } + } + + /// + /// Get stream at specified location. + /// + /// Index into record to get stream. + /// buffer to receive bytes from stream. + /// Buffer size to read. + /// Stream read into string. + public int GetStream(int field, byte[] buffer, int requestedBufferSize) + { + int bufferSize = 255; + if (requestedBufferSize > 0) + { + bufferSize = requestedBufferSize; + } + + int error = MsiInterop.MsiRecordReadStream(this.Handle, field, buffer, ref bufferSize); + if (0 != error) + { + throw new Win32Exception(error); + } + + return bufferSize; + } + + /// + /// Sets a stream at a specified location. + /// + /// Index into record to set stream. + /// Path to file to read into stream. + public void SetStream(int field, string path) + { + int error = MsiInterop.MsiRecordSetStream(this.Handle, field, path); + if (0 != error) + { + throw new Win32Exception(error); + } + } + + /// + /// Gets the number of fields in record. + /// + /// Count of fields in record. + public int GetFieldCount() + { + int size = MsiInterop.MsiRecordGetFieldCount(this.Handle); + if (0 > size) + { + throw new Win32Exception(); + } + + return size; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/Session.cs b/src/WixToolset.Core.WindowsInstaller/Msi/Session.cs new file mode 100644 index 00000000..d3a19711 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/Session.cs @@ -0,0 +1,45 @@ +// 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.Msi +{ + using System; + using System.ComponentModel; + using System.Globalization; + using WixToolset.Core.Native; + + /// + /// Controls the installation process. + /// + internal sealed class Session : MsiHandle + { + /// + /// Instantiate a new Session. + /// + /// The database to open. + public Session(Database database) + { + string packagePath = String.Format(CultureInfo.InvariantCulture, "#{0}", (uint)database.Handle); + + uint handle = 0; + int error = MsiInterop.MsiOpenPackage(packagePath, out handle); + if (0 != error) + { + throw new MsiException(error); + } + this.Handle = handle; + } + + /// + /// Executes a built-in action, custom action, or user-interface wizard action. + /// + /// Specifies the action to execute. + public void DoAction(string action) + { + int error = MsiInterop.MsiDoAction(this.Handle, action); + if (0 != error) + { + throw new MsiException(error); + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/SummaryInformation.cs b/src/WixToolset.Core.WindowsInstaller/Msi/SummaryInformation.cs new file mode 100644 index 00000000..26831731 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/SummaryInformation.cs @@ -0,0 +1,245 @@ +// 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.Msi +{ + using System; + using System.Globalization; + using System.Text; + using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + using WixToolset.Core.Native; + + /// + /// Summary information for the MSI files. + /// + internal sealed class SummaryInformation : MsiHandle + { + /// + /// Summary information properties for transforms. + /// + public enum Transform + { + /// PID_CODEPAGE = code page for the summary information stream + CodePage = 1, + + /// PID_TITLE = typically just "Transform" + Title = 2, + + /// PID_SUBJECT = original subject of target + TargetSubject = 3, + + /// PID_AUTHOR = original manufacturer of target + TargetManufacturer = 4, + + /// PID_KEYWORDS = keywords for the transform, typically including at least "Installer" + Keywords = 5, + + /// PID_COMMENTS = describes what this package does + Comments = 6, + + /// PID_TEMPLATE = target platform;language + TargetPlatformAndLanguage = 7, + + /// PID_LASTAUTHOR = updated platform;language + UpdatedPlatformAndLanguage = 8, + + /// PID_REVNUMBER = {productcode}version;{newproductcode}newversion;upgradecode + ProductCodes = 9, + + /// PID_LASTPRINTED should be null for transforms + Reserved11 = 11, + + ///.PID_CREATE_DTM = the timestamp when the transform was created + CreationTime = 12, + + /// PID_PAGECOUNT = minimum installer version + InstallerRequirement = 14, + + /// PID_CHARCOUNT = validation and error flags + ValidationFlags = 16, + + /// PID_APPNAME = the application that created the transform + CreatingApplication = 18, + + /// PID_SECURITY = whether read-only is enforced; should always be 4 for transforms + Security = 19, + } + + /// + /// Summary information properties for patches. + /// + public enum Patch + { + /// PID_CODEPAGE = code page of the summary information stream + CodePage = 1, + + /// PID_TITLE = a brief description of the package type + Title = 2, + + /// PID_SUBJECT = package name + PackageName = 3, + + /// PID_AUTHOR = manufacturer of the patch package + Manufacturer = 4, + + /// PID_KEYWORDS = alternate sources for the patch package + Sources = 5, + + /// PID_COMMENTS = general purpose of the patch package + Comments = 6, + + /// PID_TEMPLATE = semicolon delimited list of ProductCodes + ProductCodes = 7, + + /// PID_LASTAUTHOR = semicolon delimited list of transform names + TransformNames = 8, + + /// PID_REVNUMBER = GUID patch code + PatchCode = 9, + + /// PID_LASTPRINTED should be null for patches + Reserved11 = 11, + + /// PID_PAGECOUNT should be null for patches + Reserved14 = 14, + + /// PID_WORDCOUNT = minimum installer version + InstallerRequirement = 15, + + /// PID_CHARCOUNT should be null for patches + Reserved16 = 16, + + /// PID_SECURITY = read-only attribute of the patch package + Security = 19, + } + + /// + /// Summary information values for the InstallerRequirement property. + /// + public enum InstallerRequirement + { + /// Any version of the installer will do + Version10 = 1, + + /// At least 1.2 + Version12 = 2, + + /// At least 2.0 + Version20 = 3, + + /// At least 3.0 + Version30 = 4, + + /// At least 3.1 + Version31 = 5, + } + + /// + /// Instantiate a new SummaryInformation class from an open database. + /// + /// Database to retrieve summary information from. + public SummaryInformation(Database db) + { + if (null == db) + { + throw new ArgumentNullException("db"); + } + + uint handle = 0; + int error = MsiInterop.MsiGetSummaryInformation(db.Handle, null, 0, ref handle); + if (0 != error) + { + throw new MsiException(error); + } + this.Handle = handle; + } + + /// + /// Instantiate a new SummaryInformation class from a database file. + /// + /// The database file. + public SummaryInformation(string databaseFile) + { + if (null == databaseFile) + { + throw new ArgumentNullException("databaseFile"); + } + + uint handle = 0; + int error = MsiInterop.MsiGetSummaryInformation(0, databaseFile, 0, ref handle); + if (0 != error) + { + throw new MsiException(error); + } + this.Handle = handle; + } + + /// + /// Variant types in the summary information table. + /// + private enum VT : uint + { + /// Variant has not been assigned. + EMPTY = 0, + + /// Null variant type. + NULL = 1, + + /// 16-bit integer variant type. + I2 = 2, + + /// 32-bit integer variant type. + I4 = 3, + + /// String variant type. + LPSTR = 30, + + /// Date time (FILETIME, converted to Variant time) variant type. + FILETIME = 64, + } + + /// + /// Gets a summary information property. + /// + /// Index of the summary information property. + /// The summary information property. + public string GetProperty(int index) + { + uint dataType; + StringBuilder stringValue = new StringBuilder(""); + int bufSize = 0; + int intValue; + FILETIME timeValue; + timeValue.dwHighDateTime = 0; + timeValue.dwLowDateTime = 0; + + int error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize); + if (234 == error) + { + stringValue.EnsureCapacity(++bufSize); + error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize); + } + + if (0 != error) + { + throw new MsiException(error); + } + + switch ((VT)dataType) + { + case VT.EMPTY: + return String.Empty; + case VT.LPSTR: + return stringValue.ToString(); + case VT.I2: + case VT.I4: + return Convert.ToString(intValue, CultureInfo.InvariantCulture); + case VT.FILETIME: + long longFileTime = (((long)timeValue.dwHighDateTime) << 32) | unchecked((uint)timeValue.dwLowDateTime); + DateTime dateTime = DateTime.FromFileTime(longFileTime); + return dateTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); + default: + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/View.cs b/src/WixToolset.Core.WindowsInstaller/Msi/View.cs new file mode 100644 index 00000000..d6542824 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Msi/View.cs @@ -0,0 +1,189 @@ +// 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.Msi +{ + using System; + using System.ComponentModel; + using System.Globalization; + using WixToolset.Core.Native; + + /// + /// Enumeration of different modify modes. + /// + public enum ModifyView + { + /// + /// Writes current data in the cursor to a table row. Updates record if the primary + /// keys match an existing row and inserts if they do not match. Fails with a read-only + /// database. This mode cannot be used with a view containing joins. + /// + Assign = MsiInterop.MSIMODIFYASSIGN, + + /// + /// Remove a row from the table. You must first call the Fetch function with the same + /// record. Fails if the row has been deleted. Works only with read-write records. This + /// mode cannot be used with a view containing joins. + /// + Delete = MsiInterop.MSIMODIFYDELETE, + + /// + /// Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only + /// database. This mode cannot be used with a view containing joins. + /// + Insert = MsiInterop.MSIMODIFYINSERT, + + /// + /// Inserts a temporary record. The information is not persistent. Fails if a row with the + /// same primary key exists. Works only with read-write records. This mode cannot be + /// used with a view containing joins. + /// + InsertTemporary = MsiInterop.MSIMODIFYINSERTTEMPORARY, + + /// + /// Inserts or validates a record in a table. Inserts if primary keys do not match any row + /// and validates if there is a match. Fails if the record does not match the data in + /// the table. Fails if there is a record with a duplicate key that is not identical. + /// Works only with read-write records. This mode cannot be used with a view containing joins. + /// + Merge = MsiInterop.MSIMODIFYMERGE, + + /// + /// Refreshes the information in the record. Must first call Fetch with the + /// same record. Fails for a deleted row. Works with read-write and read-only records. + /// + Refresh = MsiInterop.MSIMODIFYREFRESH, + + /// + /// Updates or deletes and inserts a record into a table. Must first call Fetch with + /// the same record. Updates record if the primary keys are unchanged. Deletes old row and + /// inserts new if primary keys have changed. Fails with a read-only database. This mode cannot + /// be used with a view containing joins. + /// + Replace = MsiInterop.MSIMODIFYREPLACE, + + /// + /// Refreshes the information in the supplied record without changing the position in the + /// result set and without affecting subsequent fetch operations. The record may then + /// be used for subsequent Update, Delete, and Refresh. All primary key columns of the + /// table must be in the query and the record must have at least as many fields as the + /// query. Seek cannot be used with multi-table queries. This mode cannot be used with + /// a view containing joins. See also the remarks. + /// + Seek = MsiInterop.MSIMODIFYSEEK, + + /// + /// Updates an existing record. Non-primary keys only. Must first call Fetch. Fails with a + /// deleted record. Works only with read-write records. + /// + Update = MsiInterop.MSIMODIFYUPDATE + } + + /// + /// Wrapper class for MSI API views. + /// + internal sealed class View : MsiHandle + { + /// + /// Constructor that creates a view given a database handle and a query. + /// + /// Handle to the database to run the query on. + /// Query to be executed. + public View(Database db, string query) + { + if (null == db) + { + throw new ArgumentNullException("db"); + } + + if (null == query) + { + throw new ArgumentNullException("query"); + } + + uint handle = 0; + + int error = MsiInterop.MsiDatabaseOpenView(db.Handle, query, out handle); + if (0 != error) + { + throw new MsiException(error); + } + + this.Handle = handle; + } + + /// + /// Executes a view with no customizable parameters. + /// + public void Execute() + { + this.Execute(null); + } + + /// + /// Executes a query substituing the values from the records into the customizable parameters + /// in the view. + /// + /// Record containing parameters to be substituded into the view. + public void Execute(Record record) + { + int error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle); + if (0 != error) + { + throw new MsiException(error); + } + } + + /// + /// Fetches the next row in the view. + /// + /// Returns the fetched record; otherwise null. + public Record Fetch() + { + uint recordHandle; + + int error = MsiInterop.MsiViewFetch(this.Handle, out recordHandle); + if (259 == error) + { + return null; + } + else if (0 != error) + { + throw new MsiException(error); + } + + return new Record(recordHandle); + } + + /// + /// Updates a fetched record. + /// + /// Type of modification mode. + /// Record to be modified. + public void Modify(ModifyView type, Record record) + { + int error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle); + if (0 != error) + { + throw new MsiException(error); + } + } + + /// + /// Returns a record containing column names or definitions. + /// + /// Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES. + /// The record containing information about the column. + public Record GetColumnInfo(int columnType) + { + uint recordHandle; + + int error = MsiInterop.MsiViewGetColumnInfo(this.Handle, columnType, out recordHandle); + if (0 != error) + { + throw new MsiException(error); + } + + return new Record(recordHandle); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs new file mode 100644 index 00000000..716ea000 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -0,0 +1,36 @@ +// 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 +{ + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Core.WindowsInstaller.Inscribe; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + + internal class MsiBackend : IBackend + { + public BindResult Bind(IBindContext context) + { + var validator = Validator.CreateFromContext(context, "darice.cub"); + + var command = new BindDatabaseCommand(context, validator); + command.Execute(); + + return new BindResult(command.FileTransfers, command.ContentFilePaths); + } + + public bool Inscribe(IInscribeContext context) + { + var command = new InscribeMsiPackageCommand(context); + return command.Execute(); + } + + public Output Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs new file mode 100644 index 00000000..268213d7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -0,0 +1,34 @@ +// 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 +{ + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + + internal class MsmBackend : IBackend + { + public BindResult Bind(IBindContext context) + { + var validator = Validator.CreateFromContext(context, "mergemod.cub"); + + var command = new BindDatabaseCommand(context, validator); + command.Execute(); + + return new BindResult(command.FileTransfers, command.ContentFilePaths); + } + + public bool Inscribe(IInscribeContext context) + { + return false; + } + + public Output Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs new file mode 100644 index 00000000..4b13258b --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs @@ -0,0 +1,111 @@ +// 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 +{ + using System; + using System.ComponentModel; + using System.IO; + using WixToolset.Core.Native; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + using WixToolset.Msi; + using WixToolset.Ole32; + + internal class MspBackend : IBackend + { + public BindResult Bind(IBindContext context) + { + throw new NotImplementedException(); + } + + public bool Inscribe(IInscribeContext context) + { + throw new NotImplementedException(); + } + + public Output 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 + System.IO.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; + } + } +} \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs new file mode 100644 index 00000000..2cb7da89 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs @@ -0,0 +1,37 @@ +// 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 +{ + using System; + using WixToolset.Core.WindowsInstaller.Databases; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + + internal class MstBackend : IBackend + { + public BindResult Bind(IBindContext context) + { + var command = new BindTransformCommand(); + command.Extensions = context.Extensions; + command.TempFilesLocation = context.IntermediateFolder; + command.Transform = context.IntermediateRepresentation; + command.OutputPath = context.OutputPath; + command.Execute(); + + return new BindResult(Array.Empty(), Array.Empty()); + } + + public bool Inscribe(IInscribeContext context) + { + throw new NotImplementedException(); + } + + public Output Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs b/src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs new file mode 100644 index 00000000..c6a43bc4 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs @@ -0,0 +1,437 @@ +// 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.Ole32 +{ + using System; + using System.Runtime.InteropServices; + using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; + + /// + /// Specifies the access mode to use when opening, creating, or deleting a storage object. + /// + internal enum StorageMode + { + /// + /// Indicates that the object is read-only, meaning that modifications cannot be made. + /// + Read = 0x0, + + /// + /// Enables you to save changes to the object, but does not permit access to its data. + /// + Write = 0x1, + + /// + /// Enables access and modification of object data. + /// + ReadWrite = 0x2, + + /// + /// Specifies that subsequent openings of the object are not denied read or write access. + /// + ShareDenyNone = 0x40, + + /// + /// Prevents others from subsequently opening the object in Read mode. + /// + ShareDenyRead = 0x30, + + /// + /// Prevents others from subsequently opening the object for Write or ReadWrite access. + /// + ShareDenyWrite = 0x20, + + /// + /// Prevents others from subsequently opening the object in any mode. + /// + ShareExclusive = 0x10, + + /// + /// Opens the storage object with exclusive access to the most recently committed version. + /// + Priority = 0x40000, + + /// + /// Indicates that an existing storage object or stream should be removed before the new object replaces it. + /// + Create = 0x1000, + } + + /// + /// Wrapper for the compound storage file APIs. + /// + internal sealed class Storage : IDisposable + { + private bool disposed; + private IStorage storage; + + /// + /// Instantiate a new Storage. + /// + /// The native storage interface. + private Storage(IStorage storage) + { + this.storage = storage; + } + + /// + /// Storage destructor. + /// + ~Storage() + { + this.Dispose(); + } + + /// + /// The IEnumSTATSTG interface enumerates an array of STATSTG structures. + /// + [ComImport, Guid("0000000d-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IEnumSTATSTG + { + /// + /// Gets a specified number of STATSTG structures. + /// + /// The number of STATSTG structures requested. + /// An array of STATSTG structures returned. + /// The number of STATSTG structures retrieved in the rgelt parameter. + /// The error code. + [PreserveSig] + uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] rgelt, out uint pceltFetched); + + /// + /// Skips a specified number of STATSTG structures in the enumeration sequence. + /// + /// The number of STATSTG structures to skip. + void Skip(uint celt); + + /// + /// Resets the enumeration sequence to the beginning of the STATSTG structure array. + /// + void Reset(); + + /// + /// Creates a new enumerator that contains the same enumeration state as the current STATSTG structure enumerator. + /// + /// The cloned IEnumSTATSTG interface. + [return: MarshalAs(UnmanagedType.Interface)] + IEnumSTATSTG Clone(); + } + + /// + /// The IStorage interface supports the creation and management of structured storage objects. + /// + [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IStorage + { + /// + /// Creates and opens a stream object with the specified name contained in this storage object. + /// + /// The name of the newly created stream. + /// Specifies the access mode to use when opening the newly created stream. + /// Reserved for future use; must be zero. + /// Reserved for future use; must be zero. + /// On return, pointer to the location of the new IStream interface pointer. + void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm); + + /// + /// Opens an existing stream object within this storage object using the specified access permissions in grfMode. + /// + /// The name of the stream to open. + /// Reserved for future use; must be NULL. + /// Specifies the access mode to be assigned to the open stream. + /// Reserved for future use; must be zero. + /// A pointer to IStream pointer variable that receives the interface pointer to the newly opened stream object. + void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm); + + /// + /// Creates and opens a new storage object nested within this storage object with the specified name in the specified access mode. + /// + /// The name of the newly created storage object. + /// A value that specifies the access mode to use when opening the newly created storage object. + /// Reserved for future use; must be zero. + /// Reserved for future use; must be zero. + /// A pointer, when successful, to the location of the IStorage pointer to the newly created storage object. + void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg); + + /// + /// Opens an existing storage object with the specified name in the specified access mode. + /// + /// The name of the storage object to open. + /// Must be NULL. + /// Specifies the access mode to use when opening the storage object. + /// Must be NULL. + /// Reserved for future use; must be zero. + /// When successful, pointer to the location of an IStorage pointer to the opened storage object. + void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg); + + /// + /// Copies the entire contents of an open storage object to another storage object. + /// + /// The number of elements in the array pointed to by rgiidExclude. + /// An array of interface identifiers (IIDs) that either the caller knows about and does not want + /// copied or that the storage object does not support, but whose state the caller will later explicitly copy. + /// A string name block (refer to SNB) that specifies a block of storage or stream objects that are not to be copied to the destination. + /// A pointer to the open storage object into which this storage object is to be copied. + void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest); + + /// + /// Copies or moves a substorage or stream from this storage object to another storage object. + /// + /// The name of the element in this storage object to be moved or copied. + /// IStorage pointer to the destination storage object. + /// The new name for the element in its new storage object. + /// Specifies whether the operation should be a move (STGMOVE_MOVE) or a copy (STGMOVE_COPY). + void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags); + + /// + /// Reflects changes for a transacted storage object to the parent level. + /// + /// Controls how the changes are committed to the storage object. + void Commit(uint grfCommitFlags); + + /// + /// Discards all changes that have been made to the storage object since the last commit operation. + /// + void Revert(); + + /// + /// Returns an enumerator object that can be used to enumerate the storage and stream objects contained within this storage object. + /// + /// Reserved for future use; must be zero. + /// Reserved for future use; must be NULL. + /// Reserved for future use; must be zero. + /// Pointer to IEnumSTATSTG* pointer variable that receives the interface pointer to the new enumerator object. + void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum); + + /// + /// Removes the specified storage or stream from this storage object. + /// + /// The name of the storage or stream to be removed. + void DestroyElement(string pwcsName); + + /// + /// Renames the specified storage or stream in this storage object. + /// + /// The name of the substorage or stream to be changed. + /// The new name for the specified substorage or stream. + void RenameElement(string pwcsOldName, string pwcsNewName); + + /// + /// Sets the modification, access, and creation times of the indicated storage element, if supported by the underlying file system. + /// + /// The name of the storage object element whose times are to be modified. + /// Either the new creation time for the element or NULL if the creation time is not to be modified. + /// Either the new access time for the element or NULL if the access time is not to be modified. + /// Either the new modification time for the element or NULL if the modification time is not to be modified. + void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime); + + /// + /// Assigns the specified CLSID to this storage object. + /// + /// The CLSID that is to be associated with the storage object. + void SetClass(Guid clsid); + + /// + /// Stores up to 32 bits of state information in this storage object. + /// + /// Specifies the new values of the bits to set. + /// A binary mask indicating which bits in grfStateBits are significant in this call. + void SetStateBits(uint grfStateBits, uint grfMask); + + /// + /// Returns the STATSTG structure for this open storage object. + /// + /// On return, pointer to a STATSTG structure where this method places information about the open storage object. + /// Specifies that some of the members in the STATSTG structure are not returned, thus saving a memory allocation operation. + void Stat(out STATSTG pstatstg, uint grfStatFlag); + } + + /// + /// The IStream interface lets you read and write data to stream objects. + /// + [ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IStream + { + /// + /// Reads a specified number of bytes from the stream object into memory starting at the current seek pointer. + /// + /// A pointer to the buffer which the stream data is read into. + /// The number of bytes of data to read from the stream object. + /// A pointer to a ULONG variable that receives the actual number of bytes read from the stream object. + void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead); + + /// + /// Writes a specified number of bytes into the stream object starting at the current seek pointer. + /// + /// A pointer to the buffer that contains the data that is to be written to the stream. + /// The number of bytes of data to attempt to write into the stream. + /// A pointer to a ULONG variable where this method writes the actual number of bytes written to the stream object. + void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten); + + /// + /// Changes the seek pointer to a new location relative to the beginning of the stream, the end of the stream, or the current seek pointer. + /// + /// The displacement to be added to the location indicated by the dwOrigin parameter. + /// The origin for the displacement specified in dlibMove. + /// A pointer to the location where this method writes the value of the new seek pointer from the beginning of the stream. + void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition); + + /// + /// Changes the size of the stream object. + /// + /// Specifies the new size of the stream as a number of bytes. + void SetSize(long libNewSize); + + /// + /// Copies a specified number of bytes from the current seek pointer in the stream to the current seek pointer in another stream. + /// + /// A pointer to the destination stream. + /// The number of bytes to copy from the source stream. + /// A pointer to the location where this method writes the actual number of bytes read from the source. + /// A pointer to the location where this method writes the actual number of bytes written to the destination. + void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten); + + /// + /// Ensures that any changes made to a stream object open in transacted mode are reflected in the parent storage object. + /// + /// Controls how the changes for the stream object are committed. + void Commit(int grfCommitFlags); + + /// + /// Discards all changes that have been made to a transacted stream since the last call to IStream::Commit. + /// + void Revert(); + + /// + /// Restricts access to a specified range of bytes in the stream. + /// + /// Integer that specifies the byte offset for the beginning of the range. + /// Integer that specifies the length of the range, in bytes, to be restricted. + /// Specifies the restrictions being requested on accessing the range. + void LockRegion(long libOffset, long cb, int dwLockType); + + /// + /// Removes the access restriction on a range of bytes previously restricted with IStream::LockRegion. + /// + /// Specifies the byte offset for the beginning of the range. + /// Specifies, in bytes, the length of the range to be restricted. + /// Specifies the access restrictions previously placed on the range. + void UnlockRegion(long libOffset, long cb, int dwLockType); + + /// + /// Retrieves the STATSTG structure for this stream. + /// + /// Pointer to a STATSTG structure where this method places information about this stream object. + /// Specifies that this method does not return some of the members in the STATSTG structure, thus saving a memory allocation operation. + void Stat(out STATSTG pstatstg, int grfStatFlag); + + /// + /// Creates a new stream object that references the same bytes as the original stream but provides a separate seek pointer to those bytes. + /// + /// When successful, pointer to the location of an IStream pointer to the new stream object. + void Clone(out IStream ppstm); + } + + /// + /// Creates a new compound file storage object. + /// + /// The compound file being created. + /// Specifies the access mode to use when opening the new storage object. + /// The created Storage object. + public static Storage CreateDocFile(string storageFile, StorageMode mode) + { + IStorage storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0); + + return new Storage(storage); + } + + /// + /// Opens an existing root storage object in the file system. + /// + /// The file that contains the storage object to open. + /// Specifies the access mode to use to open the storage object. + /// The created Storage object. + public static Storage Open(string storageFile, StorageMode mode) + { + IStorage storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0); + + return new Storage(storage); + } + + /// + /// Copies the entire contents of this open storage object into another Storage object. + /// + /// The destination Storage object. + public void CopyTo(Storage destinationStorage) + { + this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage); + } + + /// + /// Opens an existing Storage object with the specified name according to the specified access mode. + /// + /// The name of the Storage object. + /// The opened Storage object. + public Storage OpenStorage(string name) + { + IStorage subStorage; + + this.storage.OpenStorage(name, null, (uint)(StorageMode.Read | StorageMode.ShareExclusive), IntPtr.Zero, 0, out subStorage); + + return new Storage(subStorage); + } + + /// + /// Disposes the managed and unmanaged objects in this object. + /// + public void Dispose() + { + if (!this.disposed) + { + Marshal.ReleaseComObject(this.storage); + + this.disposed = true; + } + + GC.SuppressFinalize(this); + } + + /// + /// The native methods. + /// + private sealed class NativeMethods + { + /// + /// Protect the constructor since this class only contains static methods. + /// + private NativeMethods() + { + } + + /// + /// Creates a new compound file storage object. + /// + /// The name for the compound file being created. + /// Specifies the access mode to use when opening the new storage object. + /// Reserved for future use; must be zero. + /// A pointer to the location of the IStorage pointer to the new storage object. + [DllImport("ole32.dll", PreserveSig = false)] + [return: MarshalAs(UnmanagedType.Interface)] + internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved); + + /// + /// Opens an existing root storage object in the file system. + /// + /// The file that contains the storage object to open. + /// Most often NULL. + /// Specifies the access mode to use to open the storage object. + /// If not NULL, pointer to a block of elements in the storage to be excluded as the storage object is opened. + /// Indicates reserved for future use; must be zero. + /// A pointer to a IStorage* pointer variable that receives the interface pointer to the opened storage. + [DllImport("ole32.dll", PreserveSig = false)] + [return: MarshalAs(UnmanagedType.Interface)] + internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Patch.cs b/src/WixToolset.Core.WindowsInstaller/Patch.cs new file mode 100644 index 00000000..67150e32 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Patch.cs @@ -0,0 +1,1245 @@ +// 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.Data +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + using WixToolset.Core.Native; + using WixToolset.Msi; + + /// + /// Contains output tables and logic for building an MSP package. + /// + public class Patch + { + private List inspectorExtensions; + private Output patch; + private TableDefinitionCollection tableDefinitions; + + public Output PatchOutput + { + get { return this.patch; } + } + + public Patch() + { + this.inspectorExtensions = new List(); + this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions()); + } + + /// + /// Adds an extension. + /// + /// The extension to add. + public void AddExtension(IInspectorExtension extension) + { + this.inspectorExtensions.Add(extension); + } + + public void Load(string patchPath) + { + this.patch = Output.Load(patchPath, false); + } + + /// + /// Include transforms in a patch. + /// + /// List of transforms to attach. + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] + public void AttachTransforms(List transforms) + { + // Track if at least one transform gets attached. + bool attachedTransform = false; + + if (transforms == null || transforms.Count == 0) + { + throw new WixException(WixErrors.PatchWithoutTransforms()); + } + + // Get the patch id from the WixPatchId table. + string patchId = null; + string clientPatchId = null; + Table wixPatchIdTable = this.patch.Tables["WixPatchId"]; + if (null != wixPatchIdTable && 0 < wixPatchIdTable.Rows.Count) + { + Row patchIdRow = wixPatchIdTable.Rows[0]; + if (null != patchIdRow) + { + patchId = patchIdRow[0].ToString(); + clientPatchId = patchIdRow[1].ToString(); + } + } + + if (null == patchId) + { + throw new WixException(WixErrors.ExpectedPatchIdInWixMsp()); + } + if (null == clientPatchId) + { + throw new WixException(WixErrors.ExpectedClientPatchIdInWixMsp()); + } + + // enumerate patch.Media to map diskId to Media row + Table patchMediaTable = patch.Tables["Media"]; + + if (null == patchMediaTable || patchMediaTable.Rows.Count == 0) + { + throw new WixException(WixErrors.ExpectedMediaRowsInWixMsp()); + } + + Hashtable mediaRows = new Hashtable(patchMediaTable.Rows.Count); + foreach (MediaRow row in patchMediaTable.Rows) + { + int media = row.DiskId; + mediaRows[media] = row; + } + + // enumerate patch.WixPatchBaseline to map baseline to diskId + Table patchBaselineTable = patch.Tables["WixPatchBaseline"]; + + int numPatchBaselineRows = (null != patchBaselineTable) ? patchBaselineTable.Rows.Count : 0; + + Hashtable baselineMedia = new Hashtable(numPatchBaselineRows); + if (patchBaselineTable != null) + { + foreach (Row row in patchBaselineTable.Rows) + { + string baseline = (string)row[0]; + int media = (int)row[1]; + int validationFlags = (int)row[2]; + if (baselineMedia.Contains(baseline)) + { + this.OnMessage(WixErrors.SamePatchBaselineId(row.SourceLineNumbers, baseline)); + } + baselineMedia[baseline] = new int[] { media, validationFlags }; + } + } + + // populate MSP summary information + Table patchSummaryInfo = patch.EnsureTable(this.tableDefinitions["_SummaryInformation"]); + + // Remove properties that will be calculated or are reserved. + for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) + { + Row row = patchSummaryInfo.Rows[i]; + switch ((SummaryInformation.Patch)row[0]) + { + case SummaryInformation.Patch.ProductCodes: + case SummaryInformation.Patch.TransformNames: + case SummaryInformation.Patch.PatchCode: + case SummaryInformation.Patch.InstallerRequirement: + case SummaryInformation.Patch.Reserved11: + case SummaryInformation.Patch.Reserved14: + case SummaryInformation.Patch.Reserved16: + patchSummaryInfo.Rows.RemoveAt(i); + break; + } + } + + // Index remaining summary properties. + SummaryInfoRowCollection summaryInfo = new SummaryInfoRowCollection(patchSummaryInfo); + + // PID_CODEPAGE + if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage)) + { + // set the code page by default to the same code page for the + // string pool in the database. + Row codePage = patchSummaryInfo.CreateRow(null); + codePage[0] = (int)SummaryInformation.Patch.CodePage; + codePage[1] = this.patch.Codepage.ToString(CultureInfo.InvariantCulture); + } + + // GUID patch code for the patch. + Row revisionRow = patchSummaryInfo.CreateRow(null); + revisionRow[0] = (int)SummaryInformation.Patch.PatchCode; + revisionRow[1] = patchId; + + // Indicates the minimum Windows Installer version that is required to install the patch. + Row wordsRow = patchSummaryInfo.CreateRow(null); + wordsRow[0] = (int)SummaryInformation.Patch.InstallerRequirement; + wordsRow[1] = ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture); + + if (!summaryInfo.Contains((int)SummaryInformation.Patch.Security)) + { + Row security = patchSummaryInfo.CreateRow(null); + security[0] = (int)SummaryInformation.Patch.Security; + security[1] = "4"; // Read-only enforced + } + + // use authored comments or default to DisplayName (required) + string comments = null; + + Table msiPatchMetadataTable = patch.Tables["MsiPatchMetadata"]; + Hashtable metadataTable = new Hashtable(); + if (null != msiPatchMetadataTable) + { + foreach (Row row in msiPatchMetadataTable.Rows) + { + metadataTable.Add(row.Fields[1].Data.ToString(), row.Fields[2].Data.ToString()); + } + + if (!summaryInfo.Contains((int)SummaryInformation.Patch.Title) && metadataTable.Contains("DisplayName")) + { + string displayName = (string)metadataTable["DisplayName"]; + + Row title = patchSummaryInfo.CreateRow(null); + title[0] = (int)SummaryInformation.Patch.Title; + title[1] = displayName; + + // default comments use DisplayName as-is (no loc) + comments = displayName; + } + + if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage) && metadataTable.Contains("CodePage")) + { + Row codePage = patchSummaryInfo.CreateRow(null); + codePage[0] = (int)SummaryInformation.Patch.CodePage; + codePage[1] = metadataTable["CodePage"]; + } + + if (!summaryInfo.Contains((int)SummaryInformation.Patch.PackageName) && metadataTable.Contains("Description")) + { + Row subject = patchSummaryInfo.CreateRow(null); + subject[0] = (int)SummaryInformation.Patch.PackageName; + subject[1] = metadataTable["Description"]; + } + + if (!summaryInfo.Contains((int)SummaryInformation.Patch.Manufacturer) && metadataTable.Contains("ManufacturerName")) + { + Row author = patchSummaryInfo.CreateRow(null); + author[0] = (int)SummaryInformation.Patch.Manufacturer; + author[1] = metadataTable["ManufacturerName"]; + } + } + + // special metadata marshalled through the build + Table wixPatchMetadataTable = patch.Tables["WixPatchMetadata"]; + Hashtable wixMetadataTable = new Hashtable(); + if (null != wixPatchMetadataTable) + { + foreach (Row row in wixPatchMetadataTable.Rows) + { + wixMetadataTable.Add(row.Fields[0].Data.ToString(), row.Fields[1].Data.ToString()); + } + + if (wixMetadataTable.Contains("Comments")) + { + comments = (string)wixMetadataTable["Comments"]; + } + } + + // write the package comments to summary info + if (!summaryInfo.Contains((int)SummaryInformation.Patch.Comments) && null != comments) + { + Row commentsRow = patchSummaryInfo.CreateRow(null); + commentsRow[0] = (int)SummaryInformation.Patch.Comments; + commentsRow[1] = comments; + } + + // enumerate transforms + Dictionary productCodes = new Dictionary(); + ArrayList transformNames = new ArrayList(); + ArrayList validTransform = new ArrayList(); + int transformCount = 0; + foreach (PatchTransform mainTransform in transforms) + { + string baseline = null; + int media = -1; + int validationFlags = 0; + + if (baselineMedia.Contains(mainTransform.Baseline)) + { + int[] baselineData = (int[])baselineMedia[mainTransform.Baseline]; + int newMedia = baselineData[0]; + if (media != -1 && media != newMedia) + { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_TransformAuthoredIntoMultipleMedia, media, newMedia)); + } + baseline = mainTransform.Baseline; + media = newMedia; + validationFlags = baselineData[1]; + } + + if (media == -1) + { + // transform's baseline not attached to any Media + continue; + } + + Table patchRefTable = patch.Tables["WixPatchRef"]; + if (patchRefTable != null && patchRefTable.Rows.Count > 0) + { + if (!Patch.ReduceTransform(mainTransform.Transform, patchRefTable)) + { + // transform has none of the content authored into this patch + continue; + } + } + + // Validate the transform doesn't break any patch specific rules. + mainTransform.Validate(); + + // ensure consistent File.Sequence within each Media + MediaRow mediaRow = (MediaRow)mediaRows[media]; + + // Ensure that files are sequenced after the last file in any transform. + Table transformMediaTable = mainTransform.Transform.Tables["Media"]; + if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) + { + foreach (MediaRow transformMediaRow in transformMediaTable.Rows) + { + if (mediaRow.LastSequence < transformMediaRow.LastSequence) + { + // The Binder will pre-increment the sequence. + mediaRow.LastSequence = transformMediaRow.LastSequence; + } + } + } + + // Use the Media/@DiskId if greater for backward compatibility. + if (mediaRow.LastSequence < mediaRow.DiskId) + { + mediaRow.LastSequence = mediaRow.DiskId; + } + + // ignore media table from transform. + mainTransform.Transform.Tables.Remove("Media"); + mainTransform.Transform.Tables.Remove("WixMedia"); + mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); + + string productCode; + Output pairedTransform = this.BuildPairedTransform(patchId, clientPatchId, mainTransform.Transform, mediaRow, validationFlags, out productCode); + productCodes[productCode] = null; + DictionaryEntry entry = new DictionaryEntry(); + entry.Key = productCode; + entry.Value = mainTransform.Transform; + validTransform.Add(entry); + + // attach these transforms to the patch object + // TODO: is this an acceptable way to auto-generate transform stream names? + string transformName = baseline + "." + (++transformCount).ToString(CultureInfo.InvariantCulture); + patch.SubStorages.Add(new SubStorage(transformName, mainTransform.Transform)); + patch.SubStorages.Add(new SubStorage("#" + transformName, pairedTransform)); + transformNames.Add(":" + transformName); + transformNames.Add(":#" + transformName); + attachedTransform = true; + } + + if (!attachedTransform) + { + throw new WixException(WixErrors.PatchWithoutValidTransforms()); + } + + // Validate that a patch authored as removable is actually removable + if (metadataTable.Contains("AllowRemoval")) + { + if ("1" == metadataTable["AllowRemoval"].ToString()) + { + ArrayList tables = Patch.GetPatchUninstallBreakingTables(); + bool result = true; + foreach (DictionaryEntry entry in validTransform) + { + result &= this.CheckUninstallableTransform(entry.Key.ToString(), (Output)entry.Value, tables); + } + + if (!result) + { + throw new WixException(WixErrors.PatchNotRemovable()); + } + } + } + + // Finish filling tables with transform-dependent data. + // Semicolon delimited list of the product codes that can accept the patch. + Table wixPatchTargetTable = patch.Tables["WixPatchTarget"]; + if (null != wixPatchTargetTable) + { + Dictionary targets = new Dictionary(); + bool replace = true; + foreach (Row wixPatchTargetRow in wixPatchTargetTable.Rows) + { + string target = wixPatchTargetRow[0].ToString(); + if (0 == String.CompareOrdinal("*", target)) + { + replace = false; + } + else + { + targets[target] = null; + } + } + + // Replace the target ProductCodes with the authored list. + if (replace) + { + productCodes = targets; + } + else + { + // Copy the authored target ProductCodes into the list. + foreach (string target in targets.Keys) + { + productCodes[target] = null; + } + } + } + + string[] uniqueProductCodes = new string[productCodes.Keys.Count]; + productCodes.Keys.CopyTo(uniqueProductCodes, 0); + + Row templateRow = patchSummaryInfo.CreateRow(null); + templateRow[0] = (int)SummaryInformation.Patch.ProductCodes; + templateRow[1] = String.Join(";", uniqueProductCodes); + + // Semicolon delimited list of transform substorage names in the order they are applied. + Row savedbyRow = patchSummaryInfo.CreateRow(null); + savedbyRow[0] = (int)SummaryInformation.Patch.TransformNames; + savedbyRow[1] = String.Join(";", (string[])transformNames.ToArray(typeof(string))); + } + + /// + /// Ensure transform is uninstallable. + /// + /// Product code in transform. + /// Transform generated by torch. + /// Tables to be checked + /// True if the transform is uninstallable + private bool CheckUninstallableTransform(string productCode, Output transform, ArrayList tables) + { + bool ret = true; + foreach (string table in tables) + { + Table wixTable = transform.Tables[table]; + if (null != wixTable) + { + foreach (Row row in wixTable.Rows) + { + if (row.Operation == RowOperation.Add) + { + ret = false; + string primaryKey = row.GetPrimaryKey('/'); + if (null == primaryKey) + { + primaryKey = string.Empty; + } + this.OnMessage(WixErrors.NewRowAddedInTable(row.SourceLineNumbers, productCode, wixTable.Name, primaryKey)); + } + } + } + } + + return ret; + } + + /// + /// Tables affect patch uninstall. + /// + /// list of tables to be checked + private static ArrayList GetPatchUninstallBreakingTables() + { + ArrayList tables = new ArrayList(); + tables.Add("AppId"); + tables.Add("BindImage"); + tables.Add("Class"); + tables.Add("Complus"); + tables.Add("CreateFolder"); + tables.Add("DuplicateFile"); + tables.Add("Environment"); + tables.Add("Extension"); + tables.Add("Font"); + tables.Add("IniFile"); + tables.Add("IsolatedComponent"); + tables.Add("LockPermissions"); + tables.Add("MIME"); + tables.Add("MoveFile"); + tables.Add("MsiLockPermissionsEx"); + tables.Add("MsiServiceConfig"); + tables.Add("MsiServiceConfigFailureActions"); + tables.Add("ODBCAttribute"); + tables.Add("ODBCDataSource"); + tables.Add("ODBCDriver"); + tables.Add("ODBCSourceAttribute"); + tables.Add("ODBCTranslator"); + tables.Add("ProgId"); + tables.Add("PublishComponent"); + tables.Add("RemoveIniFile"); + tables.Add("SelfReg"); + tables.Add("ServiceControl"); + tables.Add("ServiceInstall"); + tables.Add("TypeLib"); + tables.Add("Verb"); + + return tables; + } + + /// + /// Reduce the transform according to the patch references. + /// + /// transform generated by torch. + /// Table contains patch family filter. + /// true if the transform is not empty + public static bool ReduceTransform(Output transform, Table patchRefTable) + { + // identify sections to keep + Hashtable oldSections = new Hashtable(patchRefTable.Rows.Count); + Hashtable newSections = new Hashtable(patchRefTable.Rows.Count); + Hashtable tableKeyRows = new Hashtable(); + ArrayList sequenceList = new ArrayList(); + Hashtable componentFeatureAddsIndex = new Hashtable(); + Hashtable customActionTable = new Hashtable(); + Hashtable directoryTableAdds = new Hashtable(); + Hashtable featureTableAdds = new Hashtable(); + Hashtable keptComponents = new Hashtable(); + Hashtable keptDirectories = new Hashtable(); + Hashtable keptFeatures = new Hashtable(); + Hashtable keptLockPermissions = new Hashtable(); + Hashtable keptMsiLockPermissionExs = new Hashtable(); + + Dictionary> componentCreateFolderIndex = new Dictionary>(); + Dictionary> directoryLockPermissionsIndex = new Dictionary>(); + Dictionary> directoryMsiLockPermissionsExIndex = new Dictionary>(); + + foreach (Row patchRefRow in patchRefTable.Rows) + { + string tableName = (string)patchRefRow[0]; + string key = (string)patchRefRow[1]; + + // Short circuit filtering if all changes should be included. + if ("*" == tableName && "*" == key) + { + Patch.RemoveProductCodeFromTransform(transform); + return true; + } + + Table table = transform.Tables[tableName]; + if (table == null) + { + // table not found + continue; + } + + // index this table + if (!tableKeyRows.Contains(tableName)) + { + Hashtable newKeyRows = new Hashtable(); + foreach (Row newRow in table.Rows) + { + newKeyRows[newRow.GetPrimaryKey('/')] = newRow; + } + tableKeyRows[tableName] = newKeyRows; + } + Hashtable keyRows = (Hashtable)tableKeyRows[tableName]; + + Row row = (Row)keyRows[key]; + if (row == null) + { + // row not found + continue; + } + + // Differ.sectionDelimiter + string[] sections = row.SectionId.Split('/'); + oldSections[sections[0]] = row; + newSections[sections[1]] = row; + } + + // throw away sections not referenced + int keptRows = 0; + Table directoryTable = null; + Table featureTable = null; + Table lockPermissionsTable = null; + Table msiLockPermissionsTable = null; + + foreach (Table table in transform.Tables) + { + if ("_SummaryInformation" == table.Name) + { + continue; + } + + if (table.Name == "AdminExecuteSequence" + || table.Name == "AdminUISequence" + || table.Name == "AdvtExecuteSequence" + || table.Name == "InstallUISequence" + || table.Name == "InstallExecuteSequence") + { + sequenceList.Add(table); + continue; + } + + for (int i = 0; i < table.Rows.Count; i++) + { + Row row = table.Rows[i]; + + if (table.Name == "CreateFolder") + { + string createFolderComponentId = (string)row[1]; + + List directoryList; + if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out directoryList)) + { + directoryList = new List(); + componentCreateFolderIndex.Add(createFolderComponentId, directoryList); + } + + directoryList.Add((string)row[0]); + } + + if (table.Name == "CustomAction") + { + customActionTable.Add(row[0], row); + } + + if (table.Name == "Directory") + { + directoryTable = table; + if (RowOperation.Add == row.Operation) + { + directoryTableAdds.Add(row[0], row); + } + } + + if (table.Name == "Feature") + { + featureTable = table; + if (RowOperation.Add == row.Operation) + { + featureTableAdds.Add(row[0], row); + } + } + + if (table.Name == "FeatureComponents") + { + if (RowOperation.Add == row.Operation) + { + string featureId = (string)row[0]; + string componentId = (string)row[1]; + + if (componentFeatureAddsIndex.ContainsKey(componentId)) + { + ArrayList featureList = (ArrayList)componentFeatureAddsIndex[componentId]; + featureList.Add(featureId); + } + else + { + ArrayList featureList = new ArrayList(); + componentFeatureAddsIndex.Add(componentId, featureList); + featureList.Add(featureId); + } + } + } + + if (table.Name == "LockPermissions") + { + lockPermissionsTable = table; + if ("CreateFolder" == (string)row[1]) + { + string directoryId = (string)row[0]; + + List rowList; + if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out rowList)) + { + rowList = new List(); + directoryLockPermissionsIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (table.Name == "MsiLockPermissionsEx") + { + msiLockPermissionsTable = table; + if ("CreateFolder" == (string)row[1]) + { + string directoryId = (string)row[0]; + + List rowList; + if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out rowList)) + { + rowList = new List(); + directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (null == row.SectionId) + { + table.Rows.RemoveAt(i); + i--; + } + else + { + string[] sections = row.SectionId.Split('/'); + // ignore the row without section id. + if (0 == sections[0].Length && 0 == sections[1].Length) + { + table.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + if ("Component" == table.Name) + { + keptComponents.Add((string)row[0], row); + } + + if ("Directory" == table.Name) + { + keptDirectories.Add(row[0], row); + } + + if ("Feature" == table.Name) + { + keptFeatures.Add(row[0], row); + } + + keptRows++; + } + else + { + table.Rows.RemoveAt(i); + i--; + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + if (null != directoryTable) + { + foreach (Row componentRow in keptComponents.Values) + { + string componentId = (string)componentRow[0]; + + if (RowOperation.Add == componentRow.Operation) + { + // make sure each added component has its required directory and feature heirarchy. + string directoryId = (string)componentRow[2]; + while (null != directoryId && directoryTableAdds.ContainsKey(directoryId)) + { + Row directoryRow = (Row)directoryTableAdds[directoryId]; + + if (!keptDirectories.ContainsKey(directoryId)) + { + directoryTable.Rows.Add(directoryRow); + keptDirectories.Add(directoryRow[0], null); + keptRows++; + } + + directoryId = (string)directoryRow[1]; + } + + if (componentFeatureAddsIndex.ContainsKey(componentId)) + { + foreach (string featureId in (ArrayList)componentFeatureAddsIndex[componentId]) + { + string currentFeatureId = featureId; + while (null != currentFeatureId && featureTableAdds.ContainsKey(currentFeatureId)) + { + Row featureRow = (Row)featureTableAdds[currentFeatureId]; + + if (!keptFeatures.ContainsKey(currentFeatureId)) + { + featureTable.Rows.Add(featureRow); + keptFeatures.Add(featureRow[0], null); + keptRows++; + } + + currentFeatureId = (string)featureRow[1]; + } + } + } + } + + // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. + foreach (string keptComponentId in keptComponents.Keys) + { + List directoryList; + if (componentCreateFolderIndex.TryGetValue(keptComponentId, out directoryList)) + { + foreach (string directoryId in directoryList) + { + List lockPermissionsRowList; + if (directoryLockPermissionsIndex.TryGetValue(directoryId, out lockPermissionsRowList)) + { + foreach (Row lockPermissionsRow in lockPermissionsRowList) + { + string key = lockPermissionsRow.GetPrimaryKey('/'); + if (!keptLockPermissions.ContainsKey(key)) + { + lockPermissionsTable.Rows.Add(lockPermissionsRow); + keptLockPermissions.Add(key, null); + keptRows++; + } + } + } + + List msiLockPermissionsExRowList; + if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out msiLockPermissionsExRowList)) + { + foreach (Row msiLockPermissionsExRow in msiLockPermissionsExRowList) + { + string key = msiLockPermissionsExRow.GetPrimaryKey('/'); + if (!keptMsiLockPermissionExs.ContainsKey(key)) + { + msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); + keptMsiLockPermissionExs.Add(key, null); + keptRows++; + } + } + } + } + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + // Delete tables that are empty. + ArrayList tablesToDelete = new ArrayList(); + foreach (Table table in transform.Tables) + { + if (0 == table.Rows.Count) + { + tablesToDelete.Add(table.Name); + } + } + + // delete separately to avoid messing up enumeration + foreach (string tableName in tablesToDelete) + { + transform.Tables.Remove(tableName); + } + + return keptRows > 0; + } + + /// + /// Remove the ProductCode property from the transform. + /// + /// The transform. + /// + /// Changing the ProductCode is not supported in a patch. + /// + private static void RemoveProductCodeFromTransform(Output transform) + { + Table propertyTable = transform.Tables["Property"]; + if (null != propertyTable) + { + for (int i = 0; i < propertyTable.Rows.Count; ++i) + { + Row propertyRow = propertyTable.Rows[i]; + string property = (string)propertyRow[0]; + + if ("ProductCode" == property) + { + propertyTable.Rows.RemoveAt(i); + break; + } + } + } + } + + /// + /// Check if the section is in a PatchFamily. + /// + /// Section id in target wixout + /// Section id in upgrade wixout + /// Hashtable contains section id should be kept in the baseline wixout. + /// Hashtable contains section id should be kept in the upgrade wixout. + /// true if section in patch family + private static bool IsInPatchFamily(string oldSection, string newSection, Hashtable oldSections, Hashtable newSections) + { + bool result = false; + + if ((String.IsNullOrEmpty(oldSection) && newSections.Contains(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.Contains(oldSection))) + { + result = true; + } + else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.Contains(oldSection) || newSections.Contains(newSection))) + { + result = true; + } + + return result; + } + + /// + /// Reduce the transform sequence tables. + /// + /// ArrayList of tables to be reduced + /// Hashtable contains section id should be kept in the baseline wixout. + /// Hashtable contains section id should be kept in the target wixout. + /// Hashtable contains all the rows in the CustomAction table. + /// Number of rows left + private static int ReduceTransformSequenceTable(ArrayList sequenceList, Hashtable oldSections, Hashtable newSections, Hashtable customAction) + { + int keptRows = 0; + + foreach (Table currentTable in sequenceList) + { + for (int i = 0; i < currentTable.Rows.Count; i++) + { + Row row = currentTable.Rows[i]; + string actionName = row.Fields[0].Data.ToString(); + string[] sections = row.SectionId.Split('/'); + bool isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); + + if (row.Operation == RowOperation.None) + { + // ignore the rows without section id. + if (isSectionIdEmpty) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + currentTable.Rows.RemoveAt(i); + i--; + } + } + else if (row.Operation == RowOperation.Modify) + { + bool sequenceChanged = row.Fields[2].Modified; + bool conditionChanged = row.Fields[1].Modified; + + if (sequenceChanged && !conditionChanged) + { + keptRows++; + } + else if (!sequenceChanged && conditionChanged) + { + if (isSectionIdEmpty) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + currentTable.Rows.RemoveAt(i); + i--; + } + } + else if (sequenceChanged && conditionChanged) + { + if (isSectionIdEmpty) + { + row.Fields[1].Modified = false; + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + row.Fields[1].Modified = false; + keptRows++; + } + } + } + else if (row.Operation == RowOperation.Delete) + { + if (isSectionIdEmpty) + { + // it is a stardard action which is added by wix, we should keep this action. + row.Operation = RowOperation.None; + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + if (customAction.ContainsKey(actionName)) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else + { + // it is a stardard action, we should keep this action. + row.Operation = RowOperation.None; + keptRows++; + } + } + } + else if (row.Operation == RowOperation.Add) + { + if (isSectionIdEmpty) + { + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + if (customAction.ContainsKey(actionName)) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else + { + keptRows++; + } + } + } + } + } + + return keptRows; + } + + /// + /// Create the #transform for the given main transform. + /// + /// Patch GUID from patch authoring. + /// Easily referenced identity for this patch. + /// Transform generated by torch. + /// Media authored into patch. + /// Transform validation flags for the summary information stream. + /// Output string to receive ProductCode. + [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] + public Output BuildPairedTransform(string patchId, string clientPatchId, Output mainTransform, MediaRow mediaRow, int validationFlags, out string productCode) + { + productCode = null; + Output pairedTransform = new Output(null); + pairedTransform.Type = OutputType.Transform; + pairedTransform.Codepage = mainTransform.Codepage; + + // lookup productVersion property to correct summaryInformation + string newProductVersion = null; + Table mainPropertyTable = mainTransform.Tables["Property"]; + if (null != mainPropertyTable) + { + foreach (Row row in mainPropertyTable.Rows) + { + if ("ProductVersion" == (string)row[0]) + { + newProductVersion = (string)row[1]; + } + } + } + + // TODO: build class for manipulating SummaryInformation table + Table mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; + // add required properties + Hashtable mainSummaryRows = new Hashtable(); + foreach (Row mainSummaryRow in mainSummaryTable.Rows) + { + mainSummaryRows[mainSummaryRow[0]] = mainSummaryRow; + } + if (!mainSummaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) + { + Row mainSummaryRow = mainSummaryTable.CreateRow(null); + mainSummaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + mainSummaryRow[1] = validationFlags.ToString(CultureInfo.InvariantCulture); + } + + // copy summary information from core transform + Table pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); + foreach (Row mainSummaryRow in mainSummaryTable.Rows) + { + string value = (string)mainSummaryRow[1]; + switch ((SummaryInformation.Transform)mainSummaryRow[0]) + { + case SummaryInformation.Transform.ProductCodes: + string[] propertyData = value.Split(';'); + string oldProductVersion = propertyData[0].Substring(38); + string upgradeCode = propertyData[2]; + productCode = propertyData[0].Substring(0, 38); + if (newProductVersion == null) + { + newProductVersion = oldProductVersion; + } + + // force mainTranform to old;new;upgrade and pairedTransform to new;new;upgrade + mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); + value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); + break; + case SummaryInformation.Transform.ValidationFlags: + // use validation flags authored into the patch XML + mainSummaryRow[1] = value = validationFlags.ToString(CultureInfo.InvariantCulture); + break; + } + Row pairedSummaryRow = pairedSummaryTable.CreateRow(null); + pairedSummaryRow[0] = mainSummaryRow[0]; + pairedSummaryRow[1] = value; + } + + if (productCode == null) + { + throw new InvalidOperationException(WixStrings.EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo); + } + + // copy File table + Table mainFileTable = mainTransform.Tables["File"]; + if (null != mainFileTable && 0 < mainFileTable.Rows.Count) + { + // We require file source information. + Table mainWixFileTable = mainTransform.Tables["WixFile"]; + if (null == mainWixFileTable) + { + throw new WixException(WixErrors.AdminImageRequired(productCode)); + } + + RowDictionary mainFileRows = new RowDictionary(mainFileTable); + + Table pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); + foreach (WixFileRow mainWixFileRow in mainWixFileTable.Rows) + { + FileRow mainFileRow = mainFileRows[mainWixFileRow.File]; + + // set File.Sequence to non null to satisfy transform bind + mainFileRow.Sequence = 1; + + // delete's don't need rows in the paired transform + if (mainFileRow.Operation == RowOperation.Delete) + { + continue; + } + + FileRow pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); + pairedFileRow.Operation = RowOperation.Modify; + for (int i = 0; i < mainFileRow.Fields.Length; i++) + { + pairedFileRow[i] = mainFileRow[i]; + } + + // override authored media for patch bind + mainWixFileRow.DiskId = mediaRow.DiskId; + + // suppress any change to File.Sequence to avoid bloat + mainFileRow.Fields[7].Modified = false; + + // force File row to appear in the transform + switch (mainFileRow.Operation) + { + case RowOperation.Modify: + case RowOperation.Add: + // set msidbFileAttributesPatchAdded + pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = mainFileRow.Operation; + break; + default: + pairedFileRow.Fields[6].Modified = false; + break; + } + } + } + + // add Media row to pairedTransform + Table pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); + Row pairedMediaRow = pairedMediaTable.CreateRow(null); + pairedMediaRow.Operation = RowOperation.Add; + for (int i = 0; i < mediaRow.Fields.Length; i++) + { + pairedMediaRow[i] = mediaRow[i]; + } + + // add PatchPackage for this Media + Table pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); + pairedPackageTable.Operation = TableOperation.Add; + Row pairedPackageRow = pairedPackageTable.CreateRow(null); + pairedPackageRow.Operation = RowOperation.Add; + pairedPackageRow[0] = patchId; + pairedPackageRow[1] = mediaRow.DiskId; + + // add property to both identify client patches and whether those patches are removable or not + int allowRemoval = 0; + Table msiPatchMetadataTable = this.patch.Tables["MsiPatchMetadata"]; + if (null != msiPatchMetadataTable) + { + foreach (Row msiPatchMetadataRow in msiPatchMetadataTable.Rows) + { + // get the value of the standard AllowRemoval property, if present + string company = (string)msiPatchMetadataRow[0]; + if ((null == company || 0 == company.Length) && "AllowRemoval" == (string)msiPatchMetadataRow[1]) + { + allowRemoval = Int32.Parse((string)msiPatchMetadataRow[2], CultureInfo.InvariantCulture); + } + } + } + + // add the property to the patch transform's Property table + Table pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); + pairedPropertyTable.Operation = TableOperation.Add; + Row pairedPropertyRow = pairedPropertyTable.CreateRow(null); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = string.Concat(clientPatchId, ".AllowRemoval"); + pairedPropertyRow[1] = allowRemoval.ToString(CultureInfo.InvariantCulture); + + // add this patch code GUID to the patch transform to identify + // which patches are installed, including in multi-patch + // installations. + pairedPropertyRow = pairedPropertyTable.CreateRow(null); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = string.Concat(clientPatchId, ".PatchCode"); + pairedPropertyRow[1] = patchId; + + // add PATCHNEWPACKAGECODE to apply to admin layouts + pairedPropertyRow = pairedPropertyTable.CreateRow(null); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; + pairedPropertyRow[1] = patchId; + + // add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts + Table _summaryInformationTable = this.patch.Tables["_SummaryInformation"]; + if (null != _summaryInformationTable) + { + foreach (Row row in _summaryInformationTable.Rows) + { + if (3 == (int)row[0]) // PID_SUBJECT + { + pairedPropertyRow = pairedPropertyTable.CreateRow(null); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; + pairedPropertyRow[1] = row[1]; + } + else if (6 == (int)row[0]) // PID_COMMENTS + { + pairedPropertyRow = pairedPropertyTable.CreateRow(null); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; + pairedPropertyRow[1] = row[1]; + } + } + } + + return pairedTransform; + } + + /// + /// Sends a message to the message delegate if there is one. + /// + /// Message event arguments. + public void OnMessage(MessageEventArgs mea) + { + Messaging.Instance.OnMessage(mea); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs b/src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs new file mode 100644 index 00000000..fcd749d2 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/PatchAPI/PatchInterop.cs @@ -0,0 +1,989 @@ +// 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.PatchAPI +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Runtime.InteropServices; + using WixToolset.Core; + + /// + /// Interop class for the mspatchc.dll. + /// + internal static class PatchInterop + { + // From WinError.h in the Platform SDK + internal const ushort FACILITY_WIN32 = 7; + + /// + /// Parse a number from text in either hex or decimal. + /// + /// Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise. + /// Numeric value that source represents. + static internal UInt32 ParseHexOrDecimal(string source) + { + string value = source.Trim(); + if (String.Equals(value.Substring(0,2), "0x", StringComparison.OrdinalIgnoreCase)) + { + return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); + } + else + { + return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat); + } + } + + /// + /// Create a binary delta file. + /// + /// Name of the delta file to create. + /// Name of updated file. + /// Optional paths to updated file's symbols. + /// Optional offsets to the delta retain sections in the updated file. + /// Optional array of target files. + /// Optional array of target files' symbol paths (must match basisFiles array). + /// Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries). + /// Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries). + /// Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries). + /// Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries). + /// ApiPatchingSymbolFlags value. + /// OptimizePatchSizeForLargeFiles value. + /// Flag to indicate retain ranges were ignored due to mismatch. + /// true if delta file was created, false if whole file should be used instead. + static public bool CreateDelta( + string deltaFile, + string targetFile, + string targetSymbolPath, + string targetRetainOffsets, + string[] basisFiles, + string[] basisSymbolPaths, + string[] basisIgnoreLengths, + string[] basisIgnoreOffsets, + string[] basisRetainLengths, + string[] basisRetainOffsets, + PatchSymbolFlagsType apiPatchingSymbolFlags, + bool optimizePatchSizeForLargeFiles, + out bool retainRangesIgnored + ) + { + retainRangesIgnored = false; + if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP | PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES | PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO))) + { + throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags"); + } + + if (null == deltaFile || 0 == deltaFile.Length) + { + throw new ArgumentNullException("deltaFile"); + } + + if (null == targetFile || 0 == targetFile.Length) + { + throw new ArgumentNullException("targetFile"); + } + + if (null == basisFiles || 0 == basisFiles.Length) + { + return false; + } + uint countOldFiles = (uint) basisFiles.Length; + + if (null != basisSymbolPaths) + { + if (0 != basisSymbolPaths.Length) + { + if ((uint) basisSymbolPaths.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisSymbolPaths"); + } + } + } + // a null basisSymbolPaths is allowed. + + if (null != basisIgnoreLengths) + { + if (0 != basisIgnoreLengths.Length) + { + if ((uint) basisIgnoreLengths.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisIgnoreLengths"); + } + } + } + else + { + basisIgnoreLengths = new string[countOldFiles]; + } + + if (null != basisIgnoreOffsets) + { + if (0 != basisIgnoreOffsets.Length) + { + if ((uint) basisIgnoreOffsets.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisIgnoreOffsets"); + } + } + } + else + { + basisIgnoreOffsets = new string[countOldFiles]; + } + + if (null != basisRetainLengths) + { + if (0 != basisRetainLengths.Length) + { + if ((uint) basisRetainLengths.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisRetainLengths"); + } + } + } + else + { + basisRetainLengths = new string[countOldFiles]; + } + + if (null != basisRetainOffsets) + { + if (0 != basisRetainOffsets.Length) + { + if ((uint) basisRetainOffsets.Length != countOldFiles) + { + throw new ArgumentOutOfRangeException("basisRetainOffsets"); + } + } + } + else + { + basisRetainOffsets = new string[countOldFiles]; + } + + PatchOptionData pod = new PatchOptionData(); + pod.symbolOptionFlags = apiPatchingSymbolFlags; + pod.newFileSymbolPath = targetSymbolPath; + pod.oldFileSymbolPathArray = basisSymbolPaths; + pod.extendedOptionFlags = 0; + PatchOldFileInfoW[] oldFileInfoArray = new PatchOldFileInfoW[countOldFiles]; + string[] newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(',')); + for (uint i = 0; i < countOldFiles; ++i) + { + PatchOldFileInfoW ofi = new PatchOldFileInfoW(); + ofi.oldFileName = basisFiles[i]; + string[] ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(',')); + string[] ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(',')); + string[] retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(',')); + string[] retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(',')); + // Validate inputs + if (ignoreLengthArray.Length != ignoreOffsetArray.Length) + { + throw new ArgumentOutOfRangeException("basisIgnoreLengths"); + } + + if (retainLengthArray.Length != retainOffsetArray.Length) + { + throw new ArgumentOutOfRangeException("basisRetainLengths"); + } + + if (newRetainOffsetArray.Length != retainOffsetArray.Length) + { + // remove all retain range information + retainRangesIgnored = true; + for (uint j = 0; j < countOldFiles; ++j) + { + basisRetainLengths[j] = null; + basisRetainOffsets[j] = null; + } + retainLengthArray = new string[0]; + retainOffsetArray = new string[0]; + newRetainOffsetArray = new string[0]; + for (uint j = 0; j < oldFileInfoArray.Length; ++j) + { + oldFileInfoArray[j].retainRange = null; + } + } + + // Populate IgnoreRange structure + PatchIgnoreRange[] ignoreArray = null; + if (0 != ignoreLengthArray.Length) + { + ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length]; + for (int j = 0; j < ignoreLengthArray.Length; ++j) + { + PatchIgnoreRange ignoreRange = new PatchIgnoreRange(); + ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]); + ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]); + ignoreArray[j] = ignoreRange; + } + ofi.ignoreRange = ignoreArray; + } + + PatchRetainRange[] retainArray = null; + if (0 != newRetainOffsetArray.Length) + { + retainArray = new PatchRetainRange[retainLengthArray.Length]; + for (int j = 0; j < newRetainOffsetArray.Length; ++j) + { + PatchRetainRange retainRange = new PatchRetainRange(); + retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]); + retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]); + retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]); + retainArray[j] = retainRange; + } + ofi.retainRange = retainArray; + } + oldFileInfoArray[i] = ofi; + } + + if (CreatePatchFileExW( + countOldFiles, + oldFileInfoArray, + targetFile, + deltaFile, + PatchOptionFlags(optimizePatchSizeForLargeFiles), + pod, + null, + IntPtr.Zero)) + { + return true; + } + + // determine if this is an error or a need to use whole file. + int err = Marshal.GetLastWin32Error(); + switch(err) + { + case unchecked((int) ERROR_PATCH_BIGGER_THAN_COMPRESSED): + break; + + // too late to exclude this file -- should have been caught before + case unchecked((int) ERROR_PATCH_SAME_FILE): + default: + throw new System.ComponentModel.Win32Exception(err); + } + return false; + } + + /// + /// Extract the delta header. + /// + /// Name of delta file. + /// Name of file to create with the delta's header. + static public void ExtractDeltaHeader(string delta, string deltaHeader) + { + if (!ExtractPatchHeaderToFileW(delta, deltaHeader)) + { + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); + } + } + + /// + /// Returns the PatchOptionFlags to use. + /// + /// True if optimizing for large files. + /// PATCH_OPTION_FLAG values + static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles) + { + UInt32 flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST; + if (optimizeForLargeFiles) + { + flags |= PATCH_OPTION_USE_LZX_LARGE; + } + return flags; + } + + //--------------------------------------------------------------------- + // From PatchApi.h + //--------------------------------------------------------------------- + + // + // The following contants can be combined and used as the OptionFlags + // parameter in the patch creation apis. + + internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower) + + internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large) + internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal + internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries + internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer) + + internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports + internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks + internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image + internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same + internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower) + internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero + internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps + internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch + internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support) + internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer) + internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally) + + internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007; + + // + // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags: + // + + internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer) + internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer) + + // + // In addition to the standard Win32 error codes, the following error codes may + // be returned via GetLastError() when one of the patch APIs fails. + + internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create + internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create + internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create + internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create + internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create + internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create + + /// + /// Delegate type that the PatchAPI calls for progress notification. + /// + /// . + /// . + /// . + /// True for success + public delegate bool PatchProgressCallback( + IntPtr context, + uint currentPosition, + uint maxPosition + ); + + /// + /// Delegate type that the PatchAPI calls for patch symbol load information. + /// + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// ??? + public delegate bool PatchSymloadCallback( + uint whichFile, // 0 for new file, 1 for first old file, etc + [MarshalAs(UnmanagedType.LPStr)] string symbolFileName, + uint symType, // see SYM_TYPE in imagehlp.h + uint symbolFileCheckSum, + uint symbolFileTimeDate, + uint imageFileCheckSum, + uint imageFileTimeDate, + IntPtr context + ); + + /// + /// Wraps PATCH_IGNORE_RANGE + /// + [StructLayout(LayoutKind.Sequential)] + internal class PatchIgnoreRange + { + public uint offsetInOldFile; + public uint lengthInBytes; + } + + /// + /// Wraps PATCH_RETAIN_RANGE + /// + [StructLayout(LayoutKind.Sequential)] + internal class PatchRetainRange + { + public uint offsetInOldFile; + public uint lengthInBytes; + public uint offsetInNewFile; + } + + /// + /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion) + /// + internal class PatchOldFileInfo + { + public PatchIgnoreRange[] ignoreRange; + public PatchRetainRange[] retainRange; + } + + /// + /// Wraps PATCH_OLD_FILE_INFO_W + /// + internal class PatchOldFileInfoW : PatchOldFileInfo + { + public string oldFileName; + } + + /// + /// Wraps each PATCH_INTERLEAVE_MAP Range + /// + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)] + internal class PatchInterleaveMapRange + { + public uint oldOffset; + public uint oldLength; + public uint newLength; + } + + /// + /// Wraps PATCH_INTERLEAVE_MAP + /// + internal class PatchInterleaveMap + { + public PatchInterleaveMapRange[] ranges = null; + } + + + /// + /// Wraps PATCH_OPTION_DATA + /// + [BestFitMapping(false, ThrowOnUnmappableChar = true)] + internal class PatchOptionData + { + public PatchSymbolFlagsType symbolOptionFlags; // PATCH_SYMBOL_xxx flags + [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode + [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ] + public uint extendedOptionFlags; + public PatchSymloadCallback symLoadCallback = null; + public IntPtr symLoadContext = IntPtr.Zero; + public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer) + public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer) + } + + // + // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode) + // path argument is available, even when used with one of the Unicode APIs + // such as CreatePatchFileW. This is because the unlerlying system services + // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names. + // + + // + // A note about PATCH_RETAIN_RANGE specifiers with multiple old files: + // + // Each old version file must have the same RetainRangeCount, and the same + // retain range LengthInBytes and OffsetInNewFile values in the same order. + // Only the OffsetInOldFile values can differ between old foles for retain + // ranges. + // + + // + // The following prototypes are (some of the) interfaces for creating patches from files. + // + + /// + /// Creates a new delta. + /// + /// Size of oldFileInfoArray. + /// Target file information. + /// Name of updated file. + /// Name of delta to create. + /// PATCH_OPTION_xxx. + /// Optional PATCH_OPTION_DATA structure. + /// Delegate for progress callbacks. + /// Context for progress callback delegate. + /// true if successfull, sets Marshal.GetLastWin32Error() if not. + [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CreatePatchFileExW( + uint oldFileCount, // maximum 255 + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")] + PatchOldFileInfoW[] oldFileInfoArray, + string newFileName, // input file (required) + string patchFileName, // output file (required) + uint optionFlags, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")] + PatchOptionData optionData, + [MarshalAs (UnmanagedType.FunctionPtr)] + PatchProgressCallback progressCallback, + IntPtr context + ); + + /// + /// Extracts delta header from delta. + /// + /// Name of delta file. + /// Name of file to create with delta header. + /// true if successfull, sets Marshal.GetLastWin32Error() if not. + [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ExtractPatchHeaderToFileW( + string patchFileName, // input file + string patchHeaderFileName // output file + ); + + // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks + + /// + /// Marshals arguments for the CreatePatch~ APIs + /// + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + internal class PatchAPIMarshaler : ICustomMarshaler + { + internal static ICustomMarshaler GetInstance(string cookie) + { + return new PatchAPIMarshaler(cookie); + } + + private enum MarshalType + { + PATCH_OPTION_DATA, + PATCH_OLD_FILE_INFO_W + }; + private PatchAPIMarshaler.MarshalType marshalType; + + private PatchAPIMarshaler(string cookie) + { + this.marshalType = (PatchAPIMarshaler.MarshalType) Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie); + } + + // + // Summary: + // Returns the size of the native data to be marshaled. + // + // Returns: + // The size in bytes of the native data. + public int GetNativeDataSize() + { + return Marshal.SizeOf(typeof(IntPtr)); + } + + // + // Summary: + // Performs necessary cleanup of the managed data when it is no longer needed. + // + // Parameters: + // ManagedObj: + // The managed object to be destroyed. + public void CleanUpManagedData(object ManagedObj) + { + } + + // + // Summary: + // Performs necessary cleanup of the unmanaged data when it is no longer needed. + // + // Parameters: + // pNativeData: + // A pointer to the unmanaged data to be destroyed. + public void CleanUpNativeData(IntPtr pNativeData) + { + if (IntPtr.Zero == pNativeData) + { + return; + } + + switch (this.marshalType) + { + case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: + this.CleanUpPOD(pNativeData); + break; + default: + this.CleanUpPOFI_A(pNativeData); + break; + } + } + + // + // Summary: + // Converts the managed data to unmanaged data. + // + // Parameters: + // ManagedObj: + // The managed object to be converted. + // + // Returns: + // Returns the COM view of the managed object. + public IntPtr MarshalManagedToNative(object ManagedObj) + { + if (null == ManagedObj) + { + return IntPtr.Zero; + } + + switch(this.marshalType) + { + case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: + return this.MarshalPOD(ManagedObj as PatchOptionData); + case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: + return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]); + default: + throw new InvalidOperationException(); + } + } + + + // + // Summary: + // Converts the unmanaged data to managed data. + // + // Parameters: + // pNativeData: + // A pointer to the unmanaged data to be wrapped. + // + // Returns: + // Returns the managed view of the COM data. + public object MarshalNativeToManaged(IntPtr pNativeData) + { + return null; + } + + // Implementation ************************************************* + + // PATCH_OPTION_DATA offsets + private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32)); + private static readonly int newFileSymbolPathOffset = 2*Marshal.SizeOf(typeof(Int32)); + private static readonly int oldFileSymbolPathArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); + private static readonly int extendedOptionFlagsOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int symLoadCallbackOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int symLoadContextOffset = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int interleaveMapArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 4*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int maxLzxWindowSizeOffset = 3*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int patchOptionDataSize = 4*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); + + // PATCH_OLD_FILE_INFO offsets + private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32)); + private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); + private static readonly int ignoreRangeArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); + private static readonly int retainRangeCountOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int retainRangeArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); + private static readonly int patchOldFileInfoSize = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); + + // Methods and data used to preserve data needed for cleanup + + // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount) + private static readonly Dictionary OldFileCounts = new Dictionary(); + private static readonly object OldFileCountsLock = new object(); + + private IntPtr CreateMainStruct(int oldFileCount) + { + int nativeSize; + switch(this.marshalType) + { + case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: + nativeSize = patchOptionDataSize; + break; + case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: + nativeSize = oldFileCount*patchOldFileInfoSize; + break; + default: + throw new InvalidOperationException(); + } + + IntPtr native = Marshal.AllocCoTaskMem(nativeSize); + + lock (PatchAPIMarshaler.OldFileCountsLock) + { + PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount); + } + + return native; + } + + private static void ReleaseMainStruct(IntPtr native) + { + lock (PatchAPIMarshaler.OldFileCountsLock) + { + PatchAPIMarshaler.OldFileCounts.Remove(native); + } + Marshal.FreeCoTaskMem(native); + } + + private static int GetOldFileCount(IntPtr native) + { + lock (PatchAPIMarshaler.OldFileCountsLock) + { + return PatchAPIMarshaler.OldFileCounts[native]; + } + } + + // Helper methods + + private static IntPtr OptionalAnsiString(string managed) + { + return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed); + } + + private static IntPtr OptionalUnicodeString(string managed) + { + return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed); + } + + // string array must be of the same length as the number of old files + private static IntPtr CreateArrayOfStringA(string[] managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); + IntPtr native = Marshal.AllocCoTaskMem(size); + + for (int i = 0; i < managed.Length; ++i) + { + Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i])); + } + + return native; + } + + // string array must be of the same length as the number of old files + private static IntPtr CreateArrayOfStringW(string[] managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); + IntPtr native = Marshal.AllocCoTaskMem(size); + + for (int i = 0; i < managed.Length; ++i) + { + Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i])); + } + + return native; + } + + private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + if (null == managed.ranges) + { + return IntPtr.Zero; + } + + if (0 == managed.ranges.Length) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32)) + + managed.ranges.Length*(Marshal.SizeOf(typeof(PatchInterleaveMap)))); + WriteUInt32(native, (uint) managed.ranges.Length); + + for (int i = 0; i < managed.ranges.Length; ++i) + { + Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i*Marshal.SizeOf(typeof(PatchInterleaveMap))), false); + } + return native; + } + + private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed) + { + if (null == managed) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr))); + + for (int i = 0; i < managed.Length; ++i) + { + Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i])); + } + + return native; + } + + private static void WriteUInt32(IntPtr native, uint data) + { + Marshal.WriteInt32(native, unchecked((int) data)); + } + + private static void WriteUInt32(IntPtr native, int offset, uint data) + { + Marshal.WriteInt32(native, offset, unchecked((int) data)); + } + + // Marshal operations + + private IntPtr MarshalPOD(PatchOptionData managed) + { + if (null == managed) + { + throw new ArgumentNullException("managed"); + } + + IntPtr native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length); + Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct + WriteUInt32(native, symbolOptionFlagsOffset, (uint) managed.symbolOptionFlags); + Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath)); + Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray)); + WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags); + + // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null. + if (null == managed.symLoadCallback) + { + Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero); + } + else + { + Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback)); + } + + Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext); + Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray)); + WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize); + return native; + } + + private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed) + { + if (null == managed) + { + throw new ArgumentNullException("managed"); + } + + if (0 == managed.Length) + { + return IntPtr.Zero; + } + + IntPtr native = this.CreateMainStruct(managed.Length); + + for (int i = 0; i < managed.Length; ++i) + { + PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize)); + } + + return native; + } + + private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native) + { + PatchAPIMarshaler.MarshalPOFI(managed, native); + Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName + } + + private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native) + { + Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct + WriteUInt32(native, ignoreRangeCountOffset, + (null == managed.ignoreRange) ? 0 : (uint) managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255 + Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray + WriteUInt32(native, retainRangeCountOffset, + (null == managed.retainRange) ? 0 : (uint) managed.retainRange.Length); // RetainRangeCount // maximum 255 + Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray + } + + private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array) + { + if (null == array) + { + return IntPtr.Zero; + } + + if (0 == array.Length) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchIgnoreRange))); + + for (int i = 0; i < array.Length; ++i) + { + Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchIgnoreRange)))), false); + } + + return native; + } + + private static IntPtr MarshalPRRArray(PatchRetainRange[] array) + { + if (null == array) + { + return IntPtr.Zero; + } + + if (0 == array.Length) + { + return IntPtr.Zero; + } + + IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchRetainRange))); + + for (int i = 0; i < array.Length; ++i) + { + Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchRetainRange)))), false); + } + + return native; + } + + // CleanUp operations + + private void CleanUpPOD(IntPtr native) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset)); + + if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)) + { + for (int i = 0; i < GetOldFileCount(native); ++i) + { + Marshal.FreeCoTaskMem( + Marshal.ReadIntPtr( + Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset), + i*Marshal.SizeOf(typeof(IntPtr)))); + } + + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)); + } + + if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset)) + { + for (int i = 0; i < GetOldFileCount(native); ++i) + { + Marshal.FreeCoTaskMem( + Marshal.ReadIntPtr( + Marshal.ReadIntPtr(native, interleaveMapArrayOffset), + i*Marshal.SizeOf(typeof(IntPtr)))); + } + + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset)); + } + + PatchAPIMarshaler.ReleaseMainStruct(native); + } + + private void CleanUpPOFI_A(IntPtr native) + { + for (int i = 0; i < GetOldFileCount(native); ++i) + { + PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i*patchOldFileInfoSize)); + } + + PatchAPIMarshaler.ReleaseMainStruct(native); + } + + private static void CleanUpPOFI(IntPtr native) + { + if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset)) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset)); + } + + PatchAPIMarshaler.CleanUpPOFIH(native); + } + + private static void CleanUpPOFIH(IntPtr native) + { + if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)); + } + + if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset)) + { + Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset)); + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs new file mode 100644 index 00000000..229e75b4 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs @@ -0,0 +1,146 @@ +// 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.Collections; + using System.Collections.Specialized; + using System.Globalization; + using System.IO; + using WixToolset.Core.Cab; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Msi; + + internal class ExtractCabinetsCommand + { + public ExtractCabinetsCommand(Output output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder) + { + this.Output = output; + this.Database = database; + this.InputFilePath = inputFilePath; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + } + + private Output Output { get; } + + private Database Database { get; } + + private string InputFilePath { get; } + + private string ExportBasePath { get; } + + private string IntermediateFolder { get; } + + public void Execute() + { + string databaseBasePath = Path.GetDirectoryName(this.InputFilePath); + StringCollection cabinetFiles = new StringCollection(); + SortedList embeddedCabinets = new SortedList(); + + // index all of the cabinet files + if (OutputType.Module == this.Output.Type) + { + embeddedCabinets.Add(0, "MergeModule.CABinet"); + } + else if (null != this.Output.Tables["Media"]) + { + foreach (MediaRow mediaRow in this.Output.Tables["Media"].Rows) + { + if (null != mediaRow.Cabinet) + { + if (OutputType.Product == this.Output.Type || + (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation)) + { + if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) + { + embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); + } + else + { + cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet)); + } + } + } + } + } + + // extract the embedded cabinet files from the database + if (0 < embeddedCabinets.Count) + { + using (View streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) + { + foreach (int diskId in embeddedCabinets.Keys) + { + using (Record record = new Record(1)) + { + record.SetString(1, (string)embeddedCabinets[diskId]); + streamsView.Execute(record); + } + + using (Record record = streamsView.Fetch()) + { + if (null != record) + { + // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not case-sensitive, + // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work + string cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); + + // ensure the parent directory exists + System.IO.Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); + + using (FileStream fs = System.IO.File.Create(cabinetFile)) + { + int bytesRead; + byte[] buffer = new byte[512]; + + while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + + cabinetFiles.Add(cabinetFile); + } + else + { + // TODO: warning about missing embedded cabinet + } + } + } + } + } + + // extract the cabinet files + if (0 < cabinetFiles.Count) + { + string fileDirectory = Path.Combine(this.ExportBasePath, "File"); + + // delete the directory and its files to prevent cab extraction due to an existing file + if (Directory.Exists(fileDirectory)) + { + Directory.Delete(fileDirectory, true); + } + + // ensure the directory exists or extraction will fail + Directory.CreateDirectory(fileDirectory); + + foreach (string cabinetFile in cabinetFiles) + { + using (var extractCab = new WixExtractCab()) + { + try + { + extractCab.Extract(cabinetFile, fileDirectory); + } + catch (FileNotFoundException) + { + throw new WixException(WixErrors.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); + } + } + } + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs new file mode 100644 index 00000000..208be874 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs @@ -0,0 +1,791 @@ +// 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.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text.RegularExpressions; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Msi; + + internal class UnbindDatabaseCommand + { + public UnbindDatabaseCommand(Messaging messaging, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) + { + this.Messaging = messaging; + this.Database = database; + this.DatabasePath = databasePath; + this.OutputType = outputType; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + this.IsAdminImage = isAdminImage; + this.SuppressDemodularization = suppressDemodularization; + this.SkipSummaryInfo = skipSummaryInfo; + + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + } + + public Messaging Messaging { get; } + + public Database Database { get; } + + public string DatabasePath { get; } + + public OutputType OutputType { get; } + + public string ExportBasePath { get; } + + public string IntermediateFolder { get; } + + public bool IsAdminImage { get; } + + public bool SuppressDemodularization { get; } + + public bool SkipSummaryInfo { get; } + + public TableDefinitionCollection TableDefinitions { get; } + + private int SectionCount { get; set; } + + public Output Execute() + { + string modularizationGuid = null; + Output output = new Output(new SourceLineNumber(this.DatabasePath)); + View validationView = null; + + // set the output type + output.Type = this.OutputType; + + // get the codepage + this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); + using (StreamReader sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) + { + string line; + + while (null != (line = sr.ReadLine())) + { + string[] data = line.Split('\t'); + + if (2 == data.Length) + { + output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); + } + } + } + + // get the summary information table if it exists; it won't if unbinding a transform + if (!this.SkipSummaryInfo) + { + using (SummaryInformation summaryInformation = new SummaryInformation(this.Database)) + { + Table table = new Table(null, this.TableDefinitions["_SummaryInformation"]); + + for (int i = 1; 19 >= i; i++) + { + string value = summaryInformation.GetProperty(i); + + if (0 < value.Length) + { + Row row = table.CreateRow(output.SourceLineNumbers); + row[0] = i; + row[1] = value; + } + } + + output.Tables.Add(table); + } + } + + try + { + // open a view on the validation table if it exists + if (this.Database.TableExists("_Validation")) + { + validationView = this.Database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); + } + + // get the normal tables + using (View tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) + { + while (true) + { + using (Record tableRecord = tablesView.Fetch()) + { + if (null == tableRecord) + { + break; + } + + string tableName = tableRecord.GetString(1); + + using (View tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) + { + List columns; + using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES), + columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES)) + { + // index the primary keys + HashSet tablePrimaryKeys = new HashSet(); + using (Record primaryKeysRecord = this.Database.PrimaryKeys(tableName)) + { + int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); + + for (int i = 1; i <= primaryKeysFieldCount; i++) + { + tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); + } + } + + int columnCount = columnNameRecord.GetFieldCount(); + columns = new List(columnCount); + for (int i = 1; i <= columnCount; i++) + { + string columnName = columnNameRecord.GetString(i); + string idtType = columnTypeRecord.GetString(i); + + ColumnType columnType; + int length; + bool nullable; + + ColumnCategory columnCategory = ColumnCategory.Unknown; + ColumnModularizeType columnModularizeType = ColumnModularizeType.None; + bool primary = tablePrimaryKeys.Contains(columnName); + bool minValueSet = false; + int minValue = -1; + bool maxValueSet = false; + int maxValue = -1; + string keyTable = null; + bool keyColumnSet = false; + int keyColumn = -1; + string category = null; + string set = null; + string description = null; + + // get the column type, length, and whether its nullable + switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) + { + case 'i': + columnType = ColumnType.Number; + break; + case 'l': + columnType = ColumnType.Localized; + break; + case 's': + columnType = ColumnType.String; + break; + case 'v': + columnType = ColumnType.Object; + break; + default: + // TODO: error + columnType = ColumnType.Unknown; + break; + } + length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); + nullable = Char.IsUpper(idtType[0]); + + // try to get validation information + if (null != validationView) + { + using (Record validationRecord = new Record(2)) + { + validationRecord.SetString(1, tableName); + validationRecord.SetString(2, columnName); + + validationView.Execute(validationRecord); + } + + using (Record validationRecord = validationView.Fetch()) + { + if (null != validationRecord) + { + string validationNullable = validationRecord.GetString(3); + minValueSet = !validationRecord.IsNull(4); + minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); + maxValueSet = !validationRecord.IsNull(5); + maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); + keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); + keyColumnSet = !validationRecord.IsNull(7); + keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); + category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); + set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); + description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); + + // check the validation nullable value against the column definition + if (null == validationNullable) + { + // TODO: warn for illegal validation nullable column + } + else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) + { + // TODO: warn for mismatch between column definition and validation nullable + } + + // convert category to ColumnCategory + if (null != category) + { + try + { + columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); + } + catch (ArgumentException) + { + columnCategory = ColumnCategory.Unknown; + } + } + } + else + { + // TODO: warn about no validation information + } + } + } + + // guess the modularization type + if ("Icon" == keyTable && 1 == keyColumn) + { + columnModularizeType = ColumnModularizeType.Icon; + } + else if ("Condition" == columnName) + { + columnModularizeType = ColumnModularizeType.Condition; + } + else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) + { + columnModularizeType = ColumnModularizeType.Property; + } + else if (ColumnCategory.Identifier == columnCategory) + { + columnModularizeType = ColumnModularizeType.Column; + } + + columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); + } + } + + TableDefinition tableDefinition = new TableDefinition(tableName, columns, false, false); + + // use our table definitions if core properties are the same; this allows us to take advantage + // of wix concepts like localizable columns which current code assumes + if (this.TableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.TableDefinitions[tableName])) + { + tableDefinition = this.TableDefinitions[tableName]; + } + + Table table = new Table(null, tableDefinition); + + while (true) + { + using (Record rowRecord = tableView.Fetch()) + { + if (null == rowRecord) + { + break; + } + + int recordCount = rowRecord.GetFieldCount(); + Row row = table.CreateRow(output.SourceLineNumbers); + + for (int i = 0; recordCount > i && row.Fields.Length > i; i++) + { + if (rowRecord.IsNull(i + 1)) + { + if (!row.Fields[i].Column.Nullable) + { + // TODO: display an error for a null value in a non-nullable field OR + // display a warning and put an empty string in the value to let the compiler handle it + // (the second option is risky because the later code may make certain assumptions about + // the contents of a row value) + } + } + else + { + switch (row.Fields[i].Column.Type) + { + case ColumnType.Number: + bool success = false; + int 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); + } + + if (!success) + { + this.Messaging.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); + } + break; + case ColumnType.Object: + string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; + + if (null != this.ExportBasePath) + { + string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); + sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile); + + // ensure the parent directory exists + System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); + + using (FileStream fs = System.IO.File.Create(sourceFile)) + { + int bytesRead; + byte[] buffer = new byte[512]; + + while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + } + + row[i] = sourceFile; + break; + default: + string value = rowRecord.GetString(i + 1); + + switch (row.Fields[i].Column.Category) + { + case ColumnCategory.Guid: + value = value.ToUpper(CultureInfo.InvariantCulture); + break; + } + + // de-modularize + if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) + { + 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}"); + + if (null == modularizationGuid) + { + Match match = modularization.Match(value); + if (match.Success) + { + modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); + } + } + + value = modularization.Replace(value, String.Empty); + } + + // escape "$(" for the preprocessor + value = value.Replace("$(", "$$("); + + // escape things that look like wix variables + MatchCollection matches = Common.WixVariableRegex.Matches(value); + for (int j = matches.Count - 1; 0 <= j; j--) + { + value = value.Insert(matches[j].Index, "!"); + } + + row[i] = value; + break; + } + } + } + } + } + + output.Tables.Add(table); + } + + } + } + } + } + finally + { + if (null != validationView) + { + validationView.Close(); + } + } + + // set the modularization guid as the PackageCode + if (null != modularizationGuid) + { + Table table = output.Tables["_SummaryInformation"]; + + foreach (Row row in table.Rows) + { + if (9 == (int)row[0]) // PID_REVNUMBER + { + row[1] = modularizationGuid; + } + } + } + + if (this.IsAdminImage) + { + GenerateWixFileTable(this.DatabasePath, output); + GenerateSectionIds(output); + } + + return output; + } + + /// + /// 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, Output output) + { + string adminRootPath = Path.GetDirectoryName(databaseFile); + + Hashtable componentDirectoryIndex = new Hashtable(); + Table componentTable = output.Tables["Component"]; + foreach (Row row in componentTable.Rows) + { + componentDirectoryIndex.Add(row[0], row[2]); + } + + // Index full source paths for all directories + Hashtable directoryDirectoryParentIndex = new Hashtable(); + Hashtable directoryFullPathIndex = new Hashtable(); + Hashtable directorySourceNameIndex = new Hashtable(); + Table directoryTable = output.Tables["Directory"]; + foreach (Row 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])); + } + } + + foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) + { + if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) + { + GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); + } + } + + Table fileTable = output.Tables["File"]; + Table wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); + foreach (Row row in fileTable.Rows) + { + WixFileRow 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])); + + if (!File.Exists(wixFileRow.Source)) + { + throw new WixException(WixErrors.WixFileNotFound(wixFileRow.Source)); + } + + wixFileTable.Rows.Add(wixFileRow); + } + } + + /// + /// Gets the full path of a directory. Populates the full path index with the directory's full path and all of its parent directorie's full paths. + /// + /// The directory identifier. + /// The Hashtable containing all the directory to directory parent mapping. + /// The Hashtable containing all the directory to source name mapping. + /// The Hashtable containing a mapping between all of the directories and their previously calculated full paths. + /// The full path to the directory. + private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) + { + string parent = (string)directoryDirectoryParentIndex[directory]; + string sourceName = (string)directorySourceNameIndex[directory]; + + string parentFullPath; + if (directoryFullPathIndex.ContainsKey(parent)) + { + parentFullPath = (string)directoryFullPathIndex[parent]; + } + else + { + parentFullPath = GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); + } + + if (null == sourceName) + { + sourceName = String.Empty; + } + + string fullPath = Path.Combine(parentFullPath, sourceName); + directoryFullPathIndex.Add(directory, fullPath); + + return fullPath; + } + + /// + /// Get the source name in an admin image. + /// + /// The Filename value. + /// The source name of the directory in an admin image. + private static string GetAdminSourceName(string value) + { + string name = null; + string[] names; + string shortname = null; + string shortsourcename = null; + string sourcename = null; + + names = Common.GetNames(value); + + if (null != names[0] && "." != names[0]) + { + if (null != names[1]) + { + shortname = names[0]; + } + else + { + name = names[0]; + } + } + + if (null != names[1]) + { + name = names[1]; + } + + if (null != names[2]) + { + if (null != names[3]) + { + shortsourcename = names[2]; + } + else + { + sourcename = names[2]; + } + } + + if (null != names[3]) + { + sourcename = names[3]; + } + + if (null != sourcename) + { + return sourcename; + } + else if (null != shortsourcename) + { + return shortsourcename; + } + else if (null != name) + { + return name; + } + else + { + return shortname; + } + } + + /// + /// Creates section ids on rows which form logical groupings of resources. + /// + /// The Output that represents the msi database. + private void GenerateSectionIds(Output output) + { + // First assign and index section ids for the tables that are in their own sections. + AssignSectionIdsToTable(output.Tables["Binary"], 0); + Hashtable componentSectionIdIndex = AssignSectionIdsToTable(output.Tables["Component"], 0); + Hashtable customActionSectionIdIndex = AssignSectionIdsToTable(output.Tables["CustomAction"], 0); + AssignSectionIdsToTable(output.Tables["Directory"], 0); + Hashtable featureSectionIdIndex = AssignSectionIdsToTable(output.Tables["Feature"], 0); + AssignSectionIdsToTable(output.Tables["Icon"], 0); + Hashtable digitalCertificateSectionIdIndex = AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); + AssignSectionIdsToTable(output.Tables["Property"], 0); + + // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here. + Hashtable fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); + Hashtable appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); + Hashtable odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); + Hashtable odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); + Hashtable registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); + Hashtable serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0); + + // Now handle all the tables which only rely on previous indexes and order does not matter. + foreach (Table table in output.Tables) + { + switch (table.Name) + { + case "WixFile": + case "MsiFileHash": + ConnectTableToSection(table, fileSectionIdIndex, 0); + break; + case "MsiAssembly": + case "MsiAssemblyName": + ConnectTableToSection(table, componentSectionIdIndex, 0); + break; + case "MsiPackageCertificate": + case "MsiPatchCertificate": + ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); + break; + case "CreateFolder": + case "FeatureComponents": + case "MoveFile": + case "ReserveCost": + case "ODBCTranslator": + ConnectTableToSection(table, componentSectionIdIndex, 1); + break; + case "TypeLib": + ConnectTableToSection(table, componentSectionIdIndex, 2); + break; + case "Shortcut": + case "Environment": + ConnectTableToSection(table, componentSectionIdIndex, 3); + break; + case "RemoveRegistry": + ConnectTableToSection(table, componentSectionIdIndex, 4); + break; + case "ServiceControl": + ConnectTableToSection(table, componentSectionIdIndex, 5); + break; + case "IniFile": + case "RemoveIniFile": + ConnectTableToSection(table, componentSectionIdIndex, 7); + break; + case "AppId": + ConnectTableToSection(table, appIdSectionIdIndex, 0); + break; + case "Condition": + ConnectTableToSection(table, featureSectionIdIndex, 0); + break; + case "ODBCSourceAttribute": + ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); + break; + case "ODBCAttribute": + ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); + break; + case "AdminExecuteSequence": + case "AdminUISequence": + case "AdvtExecuteSequence": + case "AdvtUISequence": + case "InstallExecuteSequence": + case "InstallUISequence": + ConnectTableToSection(table, customActionSectionIdIndex, 0); + break; + case "LockPermissions": + case "MsiLockPermissions": + foreach (Row row in table.Rows) + { + string lockObject = (string)row[0]; + string tableName = (string)row[1]; + switch (tableName) + { + case "File": + row.SectionId = (string)fileSectionIdIndex[lockObject]; + break; + case "Registry": + row.SectionId = (string)registrySectionIdIndex[lockObject]; + break; + case "ServiceInstall": + row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; + break; + } + } + break; + } + } + + // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. + //foreach (IUnbinderExtension extension in this.unbinderExtensions) + //{ + // extension.GenerateSectionIds(output); + //} + } + + /// + /// Creates new section ids on all the rows in a table. + /// + /// 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) + { + Hashtable hashtable = new Hashtable(); + if (null != table) + { + foreach (Row row in table.Rows) + { + row.SectionId = GetNewSectionId(); + hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); + } + } + return hashtable; + } + + /// + /// Connects a table's rows to an already sectioned table. + /// + /// 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) + { + if (null != table) + { + foreach (Row row in table.Rows) + { + if (sectionIdIndex.ContainsKey(row[rowIndex])) + { + row.SectionId = (string)sectionIdIndex[row[rowIndex]]; + } + } + } + } + + /// + /// 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. + /// 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) + { + Hashtable newHashTable = new Hashtable(); + if (null != table) + { + foreach (Row row in table.Rows) + { + if (!sectionIdIndex.ContainsKey(row[rowIndex])) + { + continue; + } + + row.SectionId = (string)sectionIdIndex[row[rowIndex]]; + if (null != row[rowPrimaryKeyIndex]) + { + newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); + } + } + } + return newHashTable; + } + + /// + /// 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); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs new file mode 100644 index 00000000..f04dcefe --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs @@ -0,0 +1,53 @@ +// 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; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Msi; + + internal class UnbindMsiOrMsmCommand + { + public UnbindMsiOrMsmCommand(IUnbindContext context) + { + this.Context = context; + } + + public IUnbindContext Context { get; } + + public Output Execute() + { + Output output; + + try + { + using (Database database = new Database(this.Context.InputFilePath, OpenDatabase.ReadOnly)) + { + var unbindCommand = new UnbindDatabaseCommand(this.Context.Messaging, database, this.Context.InputFilePath, OutputType.Product, this.Context.ExportBasePath, this.Context.IntermediateFolder, this.Context.IsAdminImage, this.Context.SuppressDemodularization, skipSummaryInfo: false); + output = unbindCommand.Execute(); + + // extract the files from the cabinets + if (!String.IsNullOrEmpty(this.Context.ExportBasePath) && !this.Context.SuppressExtractCabinets) + { + var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.InputFilePath, this.Context.ExportBasePath, this.Context.IntermediateFolder); + extractCommand.Execute(); + } + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(WixErrors.OpenDatabaseFailed(this.Context.InputFilePath)); + } + + throw; + } + + return output; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs new file mode 100644 index 00000000..c0eda9c7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs @@ -0,0 +1,317 @@ +// 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.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; + using WixToolset.Core.WindowsInstaller.Databases; + using WixToolset.Data; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + using WixToolset.Msi; + + internal class UnbindTransformCommand + { + public UnbindTransformCommand(Messaging messaging, string transformFile, string exportBasePath, string intermediateFolder) + { + this.Messaging = messaging; + this.TransformFile = transformFile; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + } + + private Messaging Messaging { get; } + + private string TransformFile { get; } + + private string ExportBasePath { get; } + + private string IntermediateFolder { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string EmptyFile { get; set; } + + public Output Execute() + { + Output transform = new Output(new SourceLineNumber(this.TransformFile)); + transform.Type = OutputType.Transform; + + // get the summary information table + using (SummaryInformation summaryInformation = new SummaryInformation(this.TransformFile)) + { + Table table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + + for (int i = 1; 19 >= i; i++) + { + string value = summaryInformation.GetProperty(i); + + if (0 < value.Length) + { + Row row = table.CreateRow(transform.SourceLineNumbers); + row[0] = i; + row[1] = value; + } + } + } + + // create a schema msi which hopefully matches the table schemas in the transform + Output schemaOutput = new Output(null); + string msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); + foreach (TableDefinition tableDefinition in this.TableDefinitions) + { + // skip unreal tables and the Patch table + if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) + { + schemaOutput.EnsureTable(tableDefinition); + } + } + + Hashtable addedRows = new Hashtable(); + Table transformViewTable; + + // Bind the schema msi. + this.GenerateDatabase(schemaOutput, msiDatabaseFile); + + // apply the transform to the database and retrieve the modifications + using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) + { + // apply the transform with the ViewTransform option to collect all the modifications + msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); + + // unbind the database + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + Output transformViewOutput = unbindCommand.Execute(); + + // index the added and possibly modified rows (added rows may also appears as modified rows) + transformViewTable = transformViewOutput.Tables["_TransformView"]; + Hashtable modifiedRows = new Hashtable(); + foreach (Row row in transformViewTable.Rows) + { + string tableName = (string)row[0]; + string columnName = (string)row[1]; + string primaryKeys = (string)row[2]; + + if ("INSERT" == columnName) + { + string index = String.Concat(tableName, ':', primaryKeys); + + addedRows.Add(index, null); + } + else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row + { + string index = String.Concat(tableName, ':', primaryKeys); + + modifiedRows[index] = row; + } + } + + // create placeholder rows for modified rows to make the transform insert the updated values when its applied + foreach (Row row in modifiedRows.Values) + { + string tableName = (string)row[0]; + string columnName = (string)row[1]; + string primaryKeys = (string)row[2]; + + string index = String.Concat(tableName, ':', primaryKeys); + + // ignore information for added rows + if (!addedRows.Contains(index)) + { + Table table = schemaOutput.Tables[tableName]; + this.CreateRow(table, primaryKeys, true); + } + } + } + + // Re-bind the schema output with the placeholder rows. + this.GenerateDatabase(schemaOutput, msiDatabaseFile); + + // apply the transform to the database and retrieve the modifications + using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) + { + try + { + // apply the transform + msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All); + + // commit the database to guard against weird errors with streams + msiDatabase.Commit(); + } + catch (Win32Exception ex) + { + if (0x65B == ex.NativeErrorCode) + { + // this commonly happens when the transform was built + // against a database schema different from the internal + // table definitions + throw new WixException(WixErrors.TransformSchemaMismatch()); + } + } + + // unbind the database + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + Output output = unbindCommand.Execute(); + + // index all the rows to easily find modified rows + Hashtable rows = new Hashtable(); + foreach (Table table in output.Tables) + { + foreach (Row row in table.Rows) + { + rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); + } + } + + // process the _TransformView rows into transform rows + foreach (Row row in transformViewTable.Rows) + { + string tableName = (string)row[0]; + string columnName = (string)row[1]; + string primaryKeys = (string)row[2]; + + Table table = transform.EnsureTable(this.TableDefinitions[tableName]); + + if ("CREATE" == columnName) // added table + { + table.Operation = TableOperation.Add; + } + else if ("DELETE" == columnName) // deleted row + { + Row deletedRow = this.CreateRow(table, primaryKeys, false); + deletedRow.Operation = RowOperation.Delete; + } + else if ("DROP" == columnName) // dropped table + { + table.Operation = TableOperation.Drop; + } + else if ("INSERT" == columnName) // added row + { + string index = String.Concat(tableName, ':', primaryKeys); + Row addedRow = (Row)rows[index]; + addedRow.Operation = RowOperation.Add; + table.Rows.Add(addedRow); + } + else if (null != primaryKeys) // modified row + { + string index = String.Concat(tableName, ':', primaryKeys); + + // the _TransformView table includes information for added rows + // that looks like modified rows so it sometimes needs to be ignored + if (!addedRows.Contains(index)) + { + Row modifiedRow = (Row)rows[index]; + + // mark the field as modified + int indexOfModifiedValue = -1; + for (int i = 0; i < modifiedRow.TableDefinition.Columns.Count; ++i) + { + if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal)) + { + indexOfModifiedValue = i; + break; + } + } + modifiedRow.Fields[indexOfModifiedValue].Modified = true; + + // move the modified row into the transform the first time its encountered + if (RowOperation.None == modifiedRow.Operation) + { + modifiedRow.Operation = RowOperation.Modify; + table.Rows.Add(modifiedRow); + } + } + } + else // added column + { + ColumnDefinition column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); + column.Added = true; + } + } + } + + return transform; + } + + private void GenerateDatabase(Output output, string databaseFile) + { + var command = new GenerateDatabaseCommand(); + command.Extensions = Array.Empty(); + command.Output = output; + command.OutputPath = databaseFile; + command.KeepAddedColumns = true; + command.UseSubDirectory = false; + command.SuppressAddingValidationRows = true; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.Codepage = -1; + command.Execute(); + } + + /// + /// Create a deleted or modified row. + /// + /// The table containing the row. + /// The primary keys of the row. + /// Option to set all required fields with placeholder values. + /// The new row. + private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) + { + Row row = table.CreateRow(null); + + string[] primaryKeyParts = primaryKeys.Split('\t'); + int primaryKeyPartIndex = 0; + + for (int i = 0; i < table.Definition.Columns.Count; i++) + { + ColumnDefinition columnDefinition = table.Definition.Columns[i]; + + if (columnDefinition.PrimaryKey) + { + if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); + } + else + { + row[i] = primaryKeyParts[primaryKeyPartIndex++]; + } + } + else if (setRequiredFields) + { + if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + row[i] = 1; + } + else if (ColumnType.Object == columnDefinition.Type) + { + if (null == this.EmptyFile) + { + this.EmptyFile = Path.GetTempFileName() + ".empty"; + using (FileStream fileStream = File.Create(this.EmptyFile)) + { + } + } + + row[i] = this.EmptyFile; + } + else + { + row[i] = "1"; + } + } + } + + return row; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Validator.cs b/src/WixToolset.Core.WindowsInstaller/Validator.cs new file mode 100644 index 00000000..db66f600 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Validator.cs @@ -0,0 +1,385 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Core.Native; + using WixToolset.Msi; + using System.Linq; + using System.Reflection; + + /// + /// Runs internal consistency evaluators (ICEs) from cub files against a database. + /// + public sealed class Validator : IMessageHandler + { + private string actionName; + private StringCollection cubeFiles; + private ValidatorExtension extension; + private Output output; + private InstallUIHandler validationUIHandler; + private bool validationSessionComplete; + + /// + /// Instantiate a new Validator. + /// + public Validator() + { + this.cubeFiles = new StringCollection(); + this.extension = new ValidatorExtension(); + this.validationUIHandler = new InstallUIHandler(this.ValidationUIHandler); + } + + /// + /// Gets or sets a that directs messages from the validator. + /// + /// A that directs messages from the validator. + public ValidatorExtension Extension + { + get { return this.extension; } + set { this.extension = value; } + } + + /// + /// Gets or sets the list of ICEs to run. + /// + /// The list of ICEs. + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public ISet ICEs { get; set; } + + /// + /// Gets or sets the output used for finding source line information. + /// + /// The output used for finding source line information. + public Output Output + { + // cache Output object until validation for changes in extension + get { return this.output; } + set { this.output = value; } + } + + /// + /// Gets or sets the suppressed ICEs. + /// + /// The suppressed ICEs. + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public ISet SuppressedICEs { get; set; } + + /// + /// Sets the temporary path for the Binder. + /// + public string IntermediateFolder { private get; set; } + + /// + /// Add a cube file to the validation run. + /// + /// A cube file. + public void AddCubeFile(string cubeFile) + { + this.cubeFiles.Add(cubeFile); + } + + /// + /// Validate a database. + /// + /// The database to validate. + /// true if validation succeeded; false otherwise. + public void Validate(string databaseFile) + { + int previousUILevel = (int)InstallUILevels.Basic; + IntPtr previousHwnd = IntPtr.Zero; + InstallUIHandler previousUIHandler = null; + + if (null == databaseFile) + { + throw new ArgumentNullException("databaseFile"); + } + + // initialize the validator extension + this.extension.DatabaseFile = databaseFile; + this.extension.Output = this.output; + this.extension.InitializeValidator(); + + // Ensure the temporary files can be created. + Directory.CreateDirectory(this.IntermediateFolder); + + // copy the database to a temporary location so it can be manipulated + string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(databaseFile)); + File.Copy(databaseFile, tempDatabaseFile); + + // remove the read-only property from the temporary database + FileAttributes attributes = File.GetAttributes(tempDatabaseFile); + File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly); + + Mutex mutex = new Mutex(false, "WixValidator"); + try + { + if (!mutex.WaitOne(0, false)) + { + this.OnMessage(WixVerboses.ValidationSerialized()); + mutex.WaitOne(); + } + + using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct)) + { + bool propertyTableExists = database.TableExists("Property"); + string productCode = null; + + // remove the product code from the database before opening a session to prevent opening an installed product + if (propertyTableExists) + { + using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'")) + { + using (Record record = view.Fetch()) + { + if (null != record) + { + productCode = record.GetString(1); + + using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) + { + } + } + } + } + } + + // merge in the cube databases + foreach (string cubeFile in this.cubeFiles) + { + try + { + using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly)) + { + try + { + database.Merge(cubeDatabase, "MergeConflicts"); + } + catch + { + // ignore merge errors since they are expected in the _Validation table + } + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(WixErrors.CubeFileNotFound(cubeFile)); + } + + throw; + } + } + + // commit the database before proceeding to ensure the streams don't get confused + database.Commit(); + + // the property table may have been added to the database + // from a cub database without the proper validation rows + if (!propertyTableExists) + { + using (View view = database.OpenExecuteView("DROP table `Property`")) + { + } + } + + // get all the action names for ICEs which have not been suppressed + List actions = new List(); + using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) + { + while (true) + { + using (Record record = view.Fetch()) + { + if (null == record) + { + break; + } + + string action = record.GetString(1); + + if ((this.SuppressedICEs == null || !this.SuppressedICEs.Contains(action)) && (this.ICEs == null || this.ICEs.Contains(action))) + { + actions.Add(action); + } + } + } + } + + // disable the internal UI handler and set an external UI handler + previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd); + previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero); + + // create a session for running the ICEs + this.validationSessionComplete = false; + using (Session session = new Session(database)) + { + // add the product code back into the database + if (null != productCode) + { + // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up + using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) + { + } + + using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode))) + { + } + } + + foreach (string action in actions) + { + this.actionName = action; + try + { + session.DoAction(action); + } + catch (Win32Exception e) + { + if (!Messaging.Instance.EncounteredError) + { + throw e; + } + // TODO: Review why this was clearing the error state when an exception had happened but an error was already encountered. That's weird. + //else + //{ + // this.encounteredError = false; + //} + } + this.actionName = null; + } + + // Mark the validation session complete so we ignore any messages that MSI may fire + // during session clean-up. + this.validationSessionComplete = true; + } + } + } + catch (Win32Exception e) + { + // avoid displaying errors twice since one may have already occurred in the UI handler + if (!Messaging.Instance.EncounteredError) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + // databaseFile is not passed since during light + // this would be the temporary copy and there would be + // no final output since the error occured; during smoke + // they should know the path passed into smoke + this.OnMessage(WixErrors.ValidationFailedToOpenDatabase()); + } + else if (0x64D == e.NativeErrorCode) + { + this.OnMessage(WixErrors.ValidationFailedDueToLowMsiEngine()); + } + else if (0x654 == e.NativeErrorCode) + { + this.OnMessage(WixErrors.ValidationFailedDueToInvalidPackage()); + } + else if (0x658 == e.NativeErrorCode) + { + this.OnMessage(WixErrors.ValidationFailedDueToMultilanguageMergeModule()); + } + else if (0x659 == e.NativeErrorCode) + { + this.OnMessage(WixWarnings.ValidationFailedDueToSystemPolicy()); + } + else + { + string msgTemp = e.Message; + + if (null != this.actionName) + { + msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message); + } + + this.OnMessage(WixErrors.Win32Exception(e.NativeErrorCode, msgTemp)); + } + } + } + finally + { + Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero); + Installer.SetInternalUI(previousUILevel, ref previousHwnd); + + this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag. + + mutex.ReleaseMutex(); + this.cubeFiles.Clear(); + this.extension.FinalizeValidator(); + } + } + + /// + /// Sends a message to the message delegate if there is one. + /// + /// Message event arguments. + public void OnMessage(MessageEventArgs e) + { + Messaging.Instance.OnMessage(e); + this.extension.OnMessage(e); + } + + public static Validator CreateFromContext(IBindContext context, string cubeFilename) + { + Validator validator = null; + + // Tell the binder about the validator if validation isn't suppressed + if (!context.SuppressValidation) + { + validator = new Validator(); + validator.IntermediateFolder = Path.Combine(context.IntermediateFolder, "validate"); + + // set the default cube file + string thisPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + validator.AddCubeFile(Path.Combine(thisPath, cubeFilename)); + + // Set the ICEs + validator.ICEs = new SortedSet(context.Ices); + + // Set the suppressed ICEs and disable ICEs that have equivalent-or-better checks in WiX. + validator.SuppressedICEs = new SortedSet(context.SuppressIces.Union(new[] { "ICE08", "ICE33", "ICE47", "ICE66" })); + } + + return validator; + } + + /// + /// The validation external UI handler. + /// + /// Pointer to an application context. + /// This parameter can be used for error checking. + /// Specifies a combination of one message box style, + /// one message box icon type, one default button, and one installation message type. + /// Specifies the message text. + /// -1 for an error, 0 if no action was taken, 1 if OK, 3 to abort. + private int ValidationUIHandler(IntPtr context, uint messageType, string message) + { + try + { + // If we're getting messges during the validation session, send them to + // the extension. Otherwise, ignore the messages. + if (!this.validationSessionComplete) + { + this.extension.Log(message, this.actionName); + } + } + catch (WixException ex) + { + this.OnMessage(ex.Error); + } + + return 1; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs b/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs new file mode 100644 index 00000000..44ec3106 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/ValidatorExtension.cs @@ -0,0 +1,299 @@ +// 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 +{ + using System; + using System.Collections; + using WixToolset.Data; + + /// + /// Base class for creating a validator extension. This default implementation + /// will fire and event with the ICE name and description. + /// + public class ValidatorExtension : IMessageHandler + { + private string databaseFile; + private Hashtable indexedSourceLineNumbers; + private Output output; + private SourceLineNumber sourceLineNumbers; + + /// + /// Instantiate a new . + /// + public ValidatorExtension() + { + } + + /// + /// Gets or sets the path to the database to validate. + /// + /// The path to the database to validate. + public string DatabaseFile + { + get { return this.databaseFile; } + set { this.databaseFile = value; } + } + + /// + /// Gets or sets the for finding source line information. + /// + /// The for finding source line information. + public Output Output + { + get { return this.output; } + set { this.output = value; } + } + + /// + /// Called at the beginning of the validation of a database file. + /// + /// + /// The will set + /// before calling InitializeValidator. + /// Notes to Inheritors: When overriding + /// InitializeValidator in a derived class, be sure to call + /// the base class's InitializeValidator to thoroughly + /// initialize the extension. + /// + public virtual void InitializeValidator() + { + if (this.databaseFile != null) + { + this.sourceLineNumbers = new SourceLineNumber(databaseFile); + } + } + + /// + /// Called at the end of the validation of a database file. + /// + /// + /// The default implementation will nullify source lines. + /// Notes to Inheritors: When overriding + /// FinalizeValidator in a derived class, be sure to call + /// the base class's FinalizeValidator to thoroughly + /// finalize the extension. + /// + public virtual void FinalizeValidator() + { + this.sourceLineNumbers = null; + } + + /// + /// Logs a message from the . + /// + /// A of tab-delmited tokens + /// in the validation message. + public virtual void Log(string message) + { + this.Log(message, null); + } + + /// + /// Logs a message from the . + /// + /// A of tab-delmited tokens + /// in the validation message. + /// The name of the action to which the message + /// belongs. + /// The message cannot be null. + /// + /// The message does not contain four (4) + /// or more tab-delimited tokens. + /// + /// a tab-delimited set of tokens, + /// formatted according to Windows Installer guidelines for ICE + /// message. The following table lists what each token by index + /// should mean. + /// a name that represents the ICE + /// action that was executed (e.g. 'ICE08'). + /// + /// + /// Index + /// Description + /// + /// + /// 0 + /// Name of the ICE. + /// + /// + /// 1 + /// Message type. See the following list. + /// + /// + /// 2 + /// Detailed description. + /// + /// + /// 3 + /// Help URL or location. + /// + /// + /// 4 + /// Table name. + /// + /// + /// 5 + /// Column name. + /// + /// + /// 6 + /// This and remaining fields are primary keys + /// to identify a row. + /// + /// + /// The message types are one of the following value. + /// + /// + /// Value + /// Message Type + /// + /// + /// 0 + /// Failure message reporting the failure of the + /// ICE custom action. + /// + /// + /// 1 + /// Error message reporting database authoring that + /// case incorrect behavior. + /// + /// + /// 2 + /// Warning message reporting database authoring that + /// causes incorrect behavior in certain cases. Warnings can also + /// report unexpected side-effects of database authoring. + /// + /// + /// + /// 3 + /// Informational message. + /// + /// + /// + public virtual void Log(string message, string action) + { + if (message == null) + { + throw new ArgumentNullException("message"); + } + + string[] messageParts = message.Split('\t'); + if (3 > messageParts.Length) + { + if (null == action) + { + throw new WixException(WixErrors.UnexpectedExternalUIMessage(message)); + } + else + { + throw new WixException(WixErrors.UnexpectedExternalUIMessage(message, action)); + } + } + + SourceLineNumber messageSourceLineNumbers = null; + if (6 < messageParts.Length) + { + string[] primaryKeys = new string[messageParts.Length - 6]; + + Array.Copy(messageParts, 6, primaryKeys, 0, primaryKeys.Length); + + messageSourceLineNumbers = this.GetSourceLineNumbers(messageParts[4], primaryKeys); + } + else // use the file name as the source line information + { + messageSourceLineNumbers = this.sourceLineNumbers; + } + + switch (messageParts[1]) + { + case "0": + case "1": + this.OnMessage(WixErrors.ValidationError(messageSourceLineNumbers, messageParts[0], messageParts[2])); + break; + case "2": + this.OnMessage(WixWarnings.ValidationWarning(messageSourceLineNumbers, messageParts[0], messageParts[2])); + break; + case "3": + this.OnMessage(WixVerboses.ValidationInfo(messageParts[0], messageParts[2])); + break; + default: + throw new WixException(WixErrors.InvalidValidatorMessageType(messageParts[1])); + } + } + + /// + /// Gets the source line information (if available) for a row by its table name and primary key. + /// + /// The table name of the row. + /// The primary keys of the row. + /// The source line number information if found; null otherwise. + protected SourceLineNumber GetSourceLineNumbers(string tableName, string[] primaryKeys) + { + // source line information only exists if an output file was supplied + if (null != this.output) + { + // index the source line information if it hasn't been indexed already + if (null == this.indexedSourceLineNumbers) + { + this.indexedSourceLineNumbers = new Hashtable(); + + // index each real table + foreach (Table table in this.output.Tables) + { + // skip unreal tables + if (table.Definition.Unreal) + { + continue; + } + + // index each row + foreach (Row row in table.Rows) + { + // skip rows that don't contain source line information + if (null == row.SourceLineNumbers) + { + continue; + } + + // index the row using its table name and primary key + string primaryKey = row.GetPrimaryKey(';'); + if (null != primaryKey) + { + string key = String.Concat(table.Name, ":", primaryKey); + + if (this.indexedSourceLineNumbers.ContainsKey(key)) + { + this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); + } + else + { + this.indexedSourceLineNumbers.Add(key, row.SourceLineNumbers); + } + } + } + } + } + + return (SourceLineNumber)this.indexedSourceLineNumbers[String.Concat(tableName, ":", String.Join(";", primaryKeys))]; + } + + // use the file name as the source line information + return this.sourceLineNumbers; + } + + /// + /// Sends a message to the delegate if there is one. + /// + /// Message event arguments. + /// + /// Notes to Inheritors: When overriding OnMessage + /// in a derived class, be sure to call the base class's + /// OnMessage method so that registered delegates recieve + /// the event. + /// + public virtual void OnMessage(MessageEventArgs e) + { + Messaging.Instance.OnMessage(e); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs new file mode 100644 index 00000000..b66a4617 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs @@ -0,0 +1,50 @@ +// 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 +{ + using System; + using System.IO; + using WixToolset.Extensibility; + + internal class WindowsInstallerBackendFactory : IBackendFactory + { + public bool TryCreateBackend(string outputType, string outputFile, IBindContext context, out IBackend backend) + { + if (String.IsNullOrEmpty(outputType)) + { + outputType = Path.GetExtension(outputFile); + } + + switch (outputType.ToLowerInvariant()) + { + case "module": + case ".msm": + backend = new MsmBackend(); + return true; + + case "msipackage": + case "product": + case ".msi": + backend = new MsiBackend(); + return true; + + case "patch": + case ".msp": + backend = new MspBackend(); + return true; + + //case "patchcreation": + //case ".pcp": + // return new PatchCreationBackend(); + + case "transform": + case ".mst": + backend = new MstBackend(); + return true; + } + + backend = null; + return false; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj new file mode 100644 index 00000000..d74cb1e8 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj @@ -0,0 +1,36 @@ + + + + + + netstandard2.0 + Core Windows Installer + WiX Toolset Core Windows Installer + + + + NU1701 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WixToolset.Core/BackendContext.cs b/src/WixToolset.Core/BackendContext.cs new file mode 100644 index 00000000..7166a3a3 --- /dev/null +++ b/src/WixToolset.Core/BackendContext.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using WixToolset.Data; + + public class BackendContext + { + internal BackendContext() + { + this.Messaging = Messaging.Instance; + } + + public Messaging Messaging { get; } + } +} diff --git a/src/WixToolset.Core/Bind/BindBundleCommand.cs b/src/WixToolset.Core/Bind/BindBundleCommand.cs deleted file mode 100644 index 7ea0c830..00000000 --- a/src/WixToolset.Core/Bind/BindBundleCommand.cs +++ /dev/null @@ -1,905 +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.Bind -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using WixToolset.Bind.Bundles; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - - /// - /// Binds a this.bundle. - /// - internal class BindBundleCommand : ICommand - { - public CompressionLevel DefaultCompressionLevel { private get; set; } - - public IEnumerable Extensions { private get; set; } - - public BinderFileManagerCore FileManagerCore { private get; set; } - - public IEnumerable FileManagers { private get; set; } - - public Output Output { private get; set; } - - public string OutputPath { private get; set; } - - public string PdbFile { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public string TempFilesLocation { private get; set; } - - public WixVariableResolver WixVariableResolver { private get; set; } - - public IEnumerable FileTransfers { get; private set; } - - public IEnumerable ContentFilePaths { get; private set; } - - public void Execute() - { - this.FileTransfers = Enumerable.Empty(); - this.ContentFilePaths = Enumerable.Empty(); - - // First look for data we expect to find... Chain, WixGroups, etc. - - // We shouldn't really get past the linker phase if there are - // no group items... that means that there's no UX, no Chain, - // *and* no Containers! - Table chainPackageTable = this.GetRequiredTable("WixBundlePackage"); - - Table wixGroupTable = this.GetRequiredTable("WixGroup"); - - // Ensure there is one and only one row in the WixBundle table. - // The compiler and linker behavior should have colluded to get - // this behavior. - WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle"); - - bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user. - - // Ensure there is one and only one row in the WixBootstrapperApplication table. - // The compiler and linker behavior should have colluded to get - // this behavior. - Row baRow = this.GetSingleRowTable("WixBootstrapperApplication"); - - // Ensure there is one and only one row in the WixChain table. - // The compiler and linker behavior should have colluded to get - // this behavior. - WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain"); - - if (Messaging.Instance.EncounteredError) - { - return; - } - - // Localize fields, resolve wix variables, and resolve file paths. - ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); - - IEnumerable delayedFields; - { - ResolveFieldsCommand command = new ResolveFieldsCommand(); - command.Tables = this.Output.Tables; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.FileManagerCore = this.FileManagerCore; - command.FileManagers = this.FileManagers; - command.SupportDelayedResolution = true; - command.TempFilesLocation = this.TempFilesLocation; - command.WixVariableResolver = this.WixVariableResolver; - command.Execute(); - - delayedFields = command.DelayedFields; - } - - if (Messaging.Instance.EncounteredError) - { - return; - } - - // If there are any fields to resolve later, create the cache to populate during bind. - IDictionary variableCache = null; - if (delayedFields.Any()) - { - variableCache = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - } - - // TODO: Although the WixSearch tables are defined in the Util extension, - // the Bundle Binder has to know all about them. We hope to revisit all - // of this in the 4.0 timeframe. - IEnumerable orderedSearches = this.OrderSearches(); - - // Extract files that come from cabinet files (this does not extract files from merge modules). - { - ExtractEmbeddedFilesCommand extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand(); - extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - extractEmbeddedFilesCommand.Execute(); - } - - // Get the explicit payloads. - RowDictionary payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); - - // Update explicitly authored payloads with their parent package and container (as appropriate) - // to make it easier to gather the payloads later. - foreach (WixGroupRow row in wixGroupTable.RowsAs()) - { - if (ComplexReferenceChildType.Payload == row.ChildType) - { - WixBundlePayloadRow payload = payloads.Get(row.ChildId); - - if (ComplexReferenceParentType.Package == row.ParentType) - { - Debug.Assert(String.IsNullOrEmpty(payload.Package)); - payload.Package = row.ParentId; - } - else if (ComplexReferenceParentType.Container == row.ParentType) - { - Debug.Assert(String.IsNullOrEmpty(payload.Container)); - payload.Container = row.ParentId; - } - else if (ComplexReferenceParentType.Layout == row.ParentType) - { - payload.LayoutOnly = true; - } - } - } - - List fileTransfers = new List(); - string layoutDirectory = Path.GetDirectoryName(this.OutputPath); - - // Process the explicitly authored payloads. - ISet processedPayloads; - { - ProcessPayloadsCommand command = new ProcessPayloadsCommand(); - command.Payloads = payloads.Values; - command.DefaultPackaging = bundleRow.DefaultPackagingType; - command.LayoutDirectory = layoutDirectory; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - - processedPayloads = new HashSet(payloads.Keys); - } - - IDictionary facades; - { - GetPackageFacadesCommand command = new GetPackageFacadesCommand(); - command.PackageTable = chainPackageTable; - command.ExePackageTable = this.Output.Tables["WixBundleExePackage"]; - command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"]; - command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"]; - command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"]; - command.Execute(); - - facades = command.PackageFacades; - } - - // Process each package facade. Note this is likely to add payloads and other rows to tables so - // note that any indexes created above may be out of date now. - foreach (PackageFacade facade in facades.Values) - { - switch (facade.Package.Type) - { - case WixBundlePackageType.Exe: - { - ProcessExePackageCommand command = new ProcessExePackageCommand(); - command.AuthoredPayloads = payloads; - command.Facade = facade; - command.Execute(); - - // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer); - } - break; - - case WixBundlePackageType.Msi: - { - ProcessMsiPackageCommand command = new ProcessMsiPackageCommand(); - command.AuthoredPayloads = payloads; - command.Facade = facade; - command.FileManager = this.FileManagers.First(); - command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]); - command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]); - command.PayloadTable = this.Output.Tables["WixBundlePayload"]; - command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]); - command.Execute(); - - if (null != variableCache) - { - variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString()); - - if (null != facade.MsiPackage.Manufacturer) - { - variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer); - } - } - - } - break; - - case WixBundlePackageType.Msp: - { - ProcessMspPackageCommand command = new ProcessMspPackageCommand(); - command.AuthoredPayloads = payloads; - command.Facade = facade; - command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); - command.Execute(); - } - break; - - case WixBundlePackageType.Msu: - { - ProcessMsuPackageCommand command = new ProcessMsuPackageCommand(); - command.Facade = facade; - command.Execute(); - } - break; - } - - if (null != variableCache) - { - BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache); - } - } - - // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) - // are present. - payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); - - // Process the payloads that were added by processing the packages. - { - ProcessPayloadsCommand command = new ProcessPayloadsCommand(); - command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList(); - command.DefaultPackaging = bundleRow.DefaultPackagingType; - command.LayoutDirectory = layoutDirectory; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - - processedPayloads = null; - } - - // Set the package metadata from the payloads now that we have the complete payload information. - ILookup payloadsByPackage = payloads.Values.ToLookup(p => p.Package); - - { - foreach (PackageFacade facade in facades.Values) - { - facade.Package.Size = 0; - - IEnumerable packagePayloads = payloadsByPackage[facade.Package.WixChainItemId]; - - foreach (WixBundlePayloadRow payload in packagePayloads) - { - facade.Package.Size += payload.FileSize; - } - - if (!facade.Package.InstallSize.HasValue) - { - facade.Package.InstallSize = facade.Package.Size; - - } - - WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload]; - - if (String.IsNullOrEmpty(facade.Package.Description)) - { - facade.Package.Description = packagePayload.Description; - } - - if (String.IsNullOrEmpty(facade.Package.DisplayName)) - { - facade.Package.DisplayName = packagePayload.DisplayName; - } - } - } - - - // Give the UX payloads their embedded IDs... - int uxPayloadIndex = 0; - { - foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container)) - { - // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even - // downloaded. The current engine requires the UX to be fully present before any downloading starts, - // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads - // into the temporary UX directory correctly, so we don't allow external either. - if (PackagingType.Embedded != payload.Packaging) - { - Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); - payload.Packaging = PackagingType.Embedded; - } - - payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); - ++uxPayloadIndex; - } - - if (0 == uxPayloadIndex) - { - // If we didn't get any UX payloads, it's an error! - throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); - } - - // Give the embedded payloads without an embedded id yet an embedded id. - int payloadIndex = 0; - foreach (WixBundlePayloadRow payload in payloads.Values) - { - Debug.Assert(PackagingType.Unknown != payload.Packaging); - - if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) - { - payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex); - ++payloadIndex; - } - } - } - - // Determine patches to automatically slipstream. - { - AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand(); - command.PackageFacades = facades.Values; - command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]); - command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); - command.Execute(); - } - - // If catalog files exist, non-embedded payloads should validate with the catalogs. - IEnumerable catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs(); - - if (catalogs.Any()) - { - VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand(); - command.Catalogs = catalogs; - command.Payloads = payloads.Values; - command.Execute(); - } - - if (Messaging.Instance.EncounteredError) - { - return; - } - - IEnumerable orderedFacades; - IEnumerable boundaries; - { - OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand(); - command.Boundaries = new RowDictionary(this.Output.Tables["WixBundleRollbackBoundary"]); - command.PackageFacades = facades; - command.WixGroupTable = wixGroupTable; - command.Execute(); - - orderedFacades = command.OrderedPackageFacades; - boundaries = command.UsedRollbackBoundaries; - } - - // Resolve any delayed fields before generating the manifest. - if (delayedFields.Any()) - { - ResolveDelayedFieldsCommand resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(); - resolveDelayedFieldsCommand.OutputType = this.Output.Type; - resolveDelayedFieldsCommand.DelayedFields = delayedFields; - resolveDelayedFieldsCommand.ModularizationGuid = null; - resolveDelayedFieldsCommand.VariableCache = variableCache; - resolveDelayedFieldsCommand.Execute(); - } - - // Set the overridable bundle provider key. - this.SetBundleProviderKey(this.Output, bundleRow); - - // Import or generate dependency providers for packages in the manifest. - this.ProcessDependencyProviders(this.Output, facades); - - // Update the bundle per-machine/per-user scope based on the chained packages. - this.ResolveBundleInstallScope(bundleRow, orderedFacades); - - // Generate the core-defined BA manifest tables... - { - CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand(); - command.BundleRow = bundleRow; - command.ChainPackages = orderedFacades; - command.LastUXPayloadIndex = uxPayloadIndex; - command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs(); - command.Output = this.Output; - command.Payloads = payloads; - command.TableDefinitions = this.TableDefinitions; - command.TempFilesLocation = this.TempFilesLocation; - command.Execute(); - - WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; - payloads.Add(baManifestPayload); - } - - foreach (BinderExtension extension in this.Extensions) - { - extension.Finish(Output); - } - - // Create all the containers except the UX container first so the manifest (that goes in the UX container) - // can contain all size and hash information about the non-UX containers. - RowDictionary containers = new RowDictionary(this.Output.Tables["WixBundleContainer"]); - - ILookup payloadsByContainer = payloads.Values.ToLookup(p => p.Container); - - int attachedContainerIndex = 1; // count starts at one because UX container is "0". - - IEnumerable uxContainerPayloads = Enumerable.Empty(); - - foreach (WixBundleContainerRow container in containers.Values) - { - IEnumerable containerPayloads = payloadsByContainer[container.Id]; - - if (!containerPayloads.Any()) - { - if (container.Id != Compiler.BurnDefaultAttachedContainerId) - { - // TODO: display warning that we're ignoring container that ended up with no paylods in it. - } - } - else if (Compiler.BurnUXContainerId == container.Id) - { - container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name); - container.AttachedContainerIndex = 0; - - // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first - // in the list since that is the Payload that Burn attempts to load. - List uxPayloads = new List(); - - string baPayloadId = baRow.FieldAsString(0); - - foreach (WixBundlePayloadRow uxPayload in containerPayloads) - { - if (uxPayload.Id == baPayloadId) - { - uxPayloads.Insert(0, uxPayload); - } - else - { - uxPayloads.Add(uxPayload); - } - } - - uxContainerPayloads = uxPayloads; - } - else - { - container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name); - - // Add detached containers to the list of file transfers. - if (ContainerType.Detached == container.Type) - { - FileTransfer transfer; - if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) - { - transfer.Built = true; - fileTransfers.Add(transfer); - } - } - else // update the attached container index. - { - Debug.Assert(ContainerType.Attached == container.Type); - - container.AttachedContainerIndex = attachedContainerIndex; - ++attachedContainerIndex; - } - - this.CreateContainer(container, containerPayloads, null); - } - } - - // Create the bundle manifest then UX container. - string manifestPath = Path.Combine(this.TempFilesLocation, "bundle-manifest.xml"); - { - CreateBurnManifestCommand command = new CreateBurnManifestCommand(); - command.FileManagers = this.FileManagers; - command.Output = this.Output; - - command.BundleInfo = bundleRow; - command.Chain = chainRow; - command.Containers = containers; - command.Catalogs = catalogs; - command.ExecutableName = Path.GetFileName(this.OutputPath); - command.OrderedPackages = orderedFacades; - command.OutputPath = manifestPath; - command.RollbackBoundaries = boundaries; - command.OrderedSearches = orderedSearches; - command.Payloads = payloads; - command.UXContainerPayloads = uxContainerPayloads; - command.Execute(); - } - - WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId]; - this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath); - - // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note - // that today, the x64 Burn uses the x86 stub. - string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString(); - - string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); - string bundleTempPath = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath)); - - Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile)); - - string bundleFilename = Path.GetFileName(this.OutputPath); - if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) - { - Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename)); - } - - FileTransfer bundleTransfer; - if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer)) - { - bundleTransfer.Built = true; - fileTransfers.Add(bundleTransfer); - } - - File.Copy(stubFile, bundleTempPath, true); - File.SetAttributes(bundleTempPath, FileAttributes.Normal); - - this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow); - - // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers - // if they should be attached. - using (BurnWriter writer = BurnWriter.Open(bundleTempPath)) - { - FileInfo burnStubFile = new FileInfo(bundleTempPath); - writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId); - - // Always attach the UX container first - writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX); - - // Now append all other attached containers - foreach (WixBundleContainerRow container in containers.Values) - { - if (ContainerType.Attached == container.Type) - { - // The container was only created if it had payloads. - if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) - { - writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); - } - } - } - } - - if (null != this.PdbFile) - { - Pdb pdb = new Pdb(); - pdb.Output = Output; - pdb.Save(this.PdbFile); - } - - this.FileTransfers = fileTransfers; - this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); - } - - private Table GetRequiredTable(string tableName) - { - Table table = this.Output.Tables[tableName]; - if (null == table || 0 == table.Rows.Count) - { - throw new WixException(WixErrors.MissingBundleInformation(tableName)); - } - - return table; - } - - private Row GetSingleRowTable(string tableName) - { - Table table = this.Output.Tables[tableName]; - if (null == table || 1 != table.Rows.Count) - { - throw new WixException(WixErrors.MissingBundleInformation(tableName)); - } - - return table.Rows[0]; - } - - private List OrderSearches() - { - Dictionary allSearches = new Dictionary(); - Table wixFileSearchTable = this.Output.Tables["WixFileSearch"]; - if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) - { - foreach (Row row in wixFileSearchTable.Rows) - { - WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row); - allSearches.Add(fileSearchInfo.Id, fileSearchInfo); - } - } - - Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"]; - if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count) - { - foreach (Row row in wixRegistrySearchTable.Rows) - { - WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row); - allSearches.Add(registrySearchInfo.Id, registrySearchInfo); - } - } - - Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"]; - if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count) - { - foreach (Row row in wixComponentSearchTable.Rows) - { - WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row); - allSearches.Add(componentSearchInfo.Id, componentSearchInfo); - } - } - - Table wixProductSearchTable = this.Output.Tables["WixProductSearch"]; - if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count) - { - foreach (Row row in wixProductSearchTable.Rows) - { - WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row); - allSearches.Add(productSearchInfo.Id, productSearchInfo); - } - } - - // Merge in the variable/condition info and get the canonical ordering for - // the searches. - List orderedSearches = new List(); - Table wixSearchTable = this.Output.Tables["WixSearch"]; - if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count) - { - orderedSearches.Capacity = wixSearchTable.Rows.Count; - foreach (Row row in wixSearchTable.Rows) - { - WixSearchInfo searchInfo = allSearches[(string)row[0]]; - searchInfo.AddWixSearchRowInfo(row); - orderedSearches.Add(searchInfo); - } - } - - return orderedSearches; - } - - /// - /// Populates the variable cache with specific package properties. - /// - /// The package with properties to cache. - /// The property cache. - private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary variableCache) - { - string id = package.WixChainItemId; - - variableCache.Add(String.Concat("packageDescription.", id), package.Description); - //variableCache.Add(String.Concat("packageLanguage.", id), package.Language); - //variableCache.Add(String.Concat("packageManufacturer.", id), package.Manufacturer); - variableCache.Add(String.Concat("packageName.", id), package.DisplayName); - variableCache.Add(String.Concat("packageVersion.", id), package.Version); - } - - private void CreateContainer(WixBundleContainerRow container, IEnumerable containerPayloads, string manifestFile) - { - CreateContainerCommand command = new CreateContainerCommand(); - command.DefaultCompressionLevel = this.DefaultCompressionLevel; - command.Payloads = containerPayloads; - command.ManifestFile = manifestFile; - command.OutputPath = container.WorkingPath; - command.Execute(); - - container.Hash = command.Hash; - container.Size = command.Size; - } - - private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable facades) - { - foreach (PackageFacade facade in facades) - { - if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine) - { - Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); - - bundleInfo.PerMachine = false; - break; - } - } - - foreach (PackageFacade facade in facades) - { - // Update package scope from bundle scope if default. - if (YesNoDefaultType.Default == facade.Package.PerMachine) - { - facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; - } - - // We will only register packages in the same scope as the bundle. Warn if any packages with providers - // are in a different scope and not permanent (permanents typically don't need a ref-count). - if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count) - { - Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); - } - } - } - - private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo) - { - WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection(); - WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033); - - version.Load(bundleTempPath); - resources.Add(version); - - // Ensure the bundle info provides a full four part version. - Version fourPartVersion = new Version(bundleInfo.Version); - int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; - int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; - int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; - int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; - - if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) - { - throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version)); - } - - fourPartVersion = new Version(major, minor, build, revision); - version.FileVersion = fourPartVersion; - version.ProductVersion = fourPartVersion; - - WixToolset.Dtf.Resources.VersionStringTable strings = version[1033]; - strings["LegalCopyright"] = bundleInfo.Copyright; - strings["OriginalFilename"] = Path.GetFileName(outputPath); - strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. - strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. - - if (!String.IsNullOrEmpty(bundleInfo.Name)) - { - strings["ProductName"] = bundleInfo.Name; - strings["FileDescription"] = bundleInfo.Name; - } - - if (!String.IsNullOrEmpty(bundleInfo.Publisher)) - { - strings["CompanyName"] = bundleInfo.Publisher; - } - else - { - strings["CompanyName"] = String.Empty; - } - - if (!String.IsNullOrEmpty(bundleInfo.IconPath)) - { - Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033); - iconGroup.ReadFromFile(bundleInfo.IconPath); - resources.Add(iconGroup); - - foreach (Dtf.Resources.Resource icon in iconGroup.Icons) - { - resources.Add(icon); - } - } - - if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath)) - { - Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033); - bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath); - resources.Add(bitmap); - } - - resources.Save(bundleTempPath); - } - - #region DependencyExtension - /// - /// Imports authored dependency providers for each package in the manifest, - /// and generates dependency providers for certain package types that do not - /// have a provider defined. - /// - /// The object for the bundle. - /// An indexed collection of chained packages. - private void ProcessDependencyProviders(Output bundle, IDictionary facades) - { - // First import any authored dependencies. These may merge with imported provides from MSI packages. - Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; - if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) - { - // Add package information for each dependency provider authored into the manifest. - foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) - { - string packageId = (string)wixDependencyProviderRow[1]; - - PackageFacade facade = null; - if (facades.TryGetValue(packageId, out facade)) - { - ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow); - - if (String.IsNullOrEmpty(dependency.Key)) - { - switch (facade.Package.Type) - { - // The WixDependencyExtension allows an empty Key for MSIs and MSPs. - case WixBundlePackageType.Msi: - dependency.Key = facade.MsiPackage.ProductCode; - break; - case WixBundlePackageType.Msp: - dependency.Key = facade.MspPackage.PatchCode; - break; - } - } - - if (String.IsNullOrEmpty(dependency.Version)) - { - dependency.Version = facade.Package.Version; - } - - // If the version is still missing, a version could not be harvested from the package and was not authored. - if (String.IsNullOrEmpty(dependency.Version)) - { - Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId)); - } - - if (String.IsNullOrEmpty(dependency.DisplayName)) - { - dependency.DisplayName = facade.Package.DisplayName; - } - - if (!facade.Provides.Merge(dependency)) - { - Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); - } - } - } - } - - // Generate providers for MSI packages that still do not have providers. - foreach (PackageFacade facade in facades.Values) - { - string key = null; - - if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count) - { - key = facade.MsiPackage.ProductCode; - } - else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count) - { - key = facade.MspPackage.PatchCode; - } - - if (!String.IsNullOrEmpty(key)) - { - ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0); - - if (!facade.Provides.Merge(dependency)) - { - Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); - } - } - } - } - - /// - /// Sets the provider key for the bundle. - /// - /// The object for the bundle. - /// The containing the provider key and other information for the bundle. - private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo) - { - // From DependencyCommon.cs in the WixDependencyExtension. - const int ProvidesAttributesBundle = 0x10000; - - Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; - if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) - { - // Search the WixDependencyProvider table for the single bundle provider key. - foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) - { - object attributes = wixDependencyProviderRow[5]; - if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes)) - { - bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2]; - break; - } - } - } - - // Defaults to the bundle ID as the provider key. - } - #endregion - } -} diff --git a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core/Bind/BindDatabaseCommand.cs deleted file mode 100644 index 93af2e9a..00000000 --- a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs +++ /dev/null @@ -1,1311 +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.Bind -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Bind.Databases; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - using WixToolset.Msi; - - /// - /// Binds a databse. - /// - internal class BindDatabaseCommand : ICommand - { - // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. - private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); - - public int Codepage { private get; set; } - - public int CabbingThreadCount { private get; set; } - - public CompressionLevel DefaultCompressionLevel { private get; set; } - - public bool DeltaBinaryPatch { get; set; } - - public IEnumerable Extensions { private get; set; } - - public BinderFileManagerCore FileManagerCore { private get; set; } - - public IEnumerable FileManagers { private get; set; } - - public IEnumerable InspectorExtensions { private get; set; } - - public Localizer Localizer { private get; set; } - - public string PdbFile { private get; set; } - - public Output Output { private get; set; } - - public string OutputPath { private get; set; } - - public bool SuppressAddingValidationRows { private get; set; } - - public bool SuppressLayout { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public string TempFilesLocation { private get; set; } - - public Validator Validator { private get; set; } - - public WixVariableResolver WixVariableResolver { private get; set; } - - public IEnumerable FileTransfers { get; private set; } - - public IEnumerable ContentFilePaths { get; private set; } - - public void Execute() - { - List fileTransfers = new List(); - - HashSet suppressedTableNames = new HashSet(); - - // Localize fields, resolve wix variables, and resolve file paths. - ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); - - IEnumerable delayedFields; - { - ResolveFieldsCommand command = new ResolveFieldsCommand(); - command.Tables = this.Output.Tables; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.FileManagerCore = this.FileManagerCore; - command.FileManagers = this.FileManagers; - command.SupportDelayedResolution = true; - command.TempFilesLocation = this.TempFilesLocation; - command.WixVariableResolver = this.WixVariableResolver; - command.Execute(); - - delayedFields = command.DelayedFields; - } - - if (OutputType.Patch == this.Output.Type) - { - foreach (SubStorage transform in this.Output.SubStorages) - { - ResolveFieldsCommand command = new ResolveFieldsCommand(); - command.Tables = transform.Data.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(); - } - } - - // If there are any fields to resolve later, create the cache to populate during bind. - IDictionary variableCache = null; - if (delayedFields.Any()) - { - variableCache = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - } - - this.LocalizeUI(this.Output.Tables); - - // Process the summary information table before the other tables. - bool compressed; - bool longNames; - int installerVersion; - string modularizationGuid; - { - BindSummaryInfoCommand command = new BindSummaryInfoCommand(); - command.Output = this.Output; - command.Execute(); - - compressed = command.Compressed; - longNames = command.LongNames; - installerVersion = command.InstallerVersion; - modularizationGuid = command.ModularizationGuid; - } - - // Stop processing if an error previously occurred. - if (Messaging.Instance.EncounteredError) - { - return; - } - - // Modularize identifiers and add tables with real streams to the import tables. - if (OutputType.Module == this.Output.Type) - { - // Gather all the suppress modularization identifiers - HashSet suppressModularizationIdentifiers = null; - Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"]; - if (null != wixSuppressModularizationTable) - { - suppressModularizationIdentifiers = new HashSet(wixSuppressModularizationTable.Rows.Select(row => (string)row[0])); - } - - foreach (Table table in this.Output.Tables) - { - table.Modularize(modularizationGuid, suppressModularizationIdentifiers); - } - } - - // This must occur after all variables and source paths have been resolved and after modularization. - List fileFacades; - { - GetFileFacadesCommand command = new GetFileFacadesCommand(); - command.FileTable = this.Output.Tables["File"]; - command.WixFileTable = this.Output.Tables["WixFile"]; - command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"]; - command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"]; - command.Execute(); - - fileFacades = command.FileFacades; - } - - ////if (OutputType.Patch == this.Output.Type) - ////{ - //// foreach (SubStorage substorage in this.Output.SubStorages) - //// { - //// Output transform = substorage.Data; - - //// ResolveFieldsCommand command = new ResolveFieldsCommand(); - //// command.Tables = transform.Tables; - //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - //// command.FileManagerCore = this.FileManagerCore; - //// command.FileManagers = this.FileManagers; - //// command.SupportDelayedResolution = false; - //// command.TempFilesLocation = this.TempFilesLocation; - //// command.WixVariableResolver = this.WixVariableResolver; - //// command.Execute(); - - //// this.MergeUnrealTables(transform.Tables); - //// } - ////} - - { - CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand(); - command.PropertyTable = this.Output.Tables["Property"]; - command.WixPropertyTable = this.Output.Tables["WixProperty"]; - command.Execute(); - } - - if (Messaging.Instance.EncounteredError) - { - return; - } - - // Add binder variables for all properties. - Table propertyTable = this.Output.Tables["Property"]; - if (null != propertyTable) - { - foreach (PropertyRow propertyRow in propertyTable.Rows) - { - // Set the ProductCode if it is to be generated. - if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) - { - propertyRow.Value = Common.GenerateGuid(); - - // Update the target ProductCode in any instance transforms. - foreach (SubStorage subStorage in this.Output.SubStorages) - { - Output subStorageOutput = subStorage.Data; - if (OutputType.Transform != subStorageOutput.Type) - { - continue; - } - - Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; - foreach (Row row in instanceSummaryInformationTable.Rows) - { - if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) - { - row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); - break; - } - } - } - } - - // Add the property name and value to the variableCache. - if (null != variableCache) - { - string key = String.Concat("property.", Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property)); - variableCache[key] = propertyRow.Value; - } - } - } - - // Extract files that come from cabinet files (this does not extract files from merge modules). - { - ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand(); - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.Execute(); - } - - if (OutputType.Product == this.Output.Type) - { - // Retrieve files and their information from merge modules. - Table wixMergeTable = this.Output.Tables["WixMerge"]; - - if (null != wixMergeTable) - { - ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand(); - command.FileFacades = fileFacades; - command.FileTable = this.Output.Tables["File"]; - command.WixFileTable = this.Output.Tables["WixFile"]; - command.WixMergeTable = wixMergeTable; - command.OutputInstallerVersion = installerVersion; - command.SuppressLayout = this.SuppressLayout; - command.TempFilesLocation = this.TempFilesLocation; - command.Execute(); - - fileFacades.AddRange(command.MergeModulesFileFacades); - } - } - else if (OutputType.Patch == this.Output.Type) - { - // Merge transform data into the output object. - IEnumerable filesFromTransform = this.CopyFromTransformData(this.Output); - - fileFacades.AddRange(filesFromTransform); - } - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - - Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation()); - - // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table). - { - UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); - command.FileFacades = fileFacades; - command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); - command.ModularizationGuid = modularizationGuid; - command.Output = this.Output; - command.OverwriteHash = true; - command.TableDefinitions = this.TableDefinitions; - command.VariableCache = variableCache; - command.Execute(); - } - - // Set generated component guids. - this.SetComponentGuids(this.Output); - - // With the Component Guids set now we can create instance transforms. - this.CreateInstanceTransforms(this.Output); - - this.ValidateComponentGuids(this.Output); - - this.UpdateControlText(this.Output); - - if (delayedFields.Any()) - { - ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand(); - command.OutputType = this.Output.Type; - command.DelayedFields = delayedFields; - command.ModularizationGuid = null; - command.VariableCache = variableCache; - command.Execute(); - } - - // Assign files to media. - RowDictionary assignedMediaRows; - Dictionary> filesByCabinetMedia; - IEnumerable uncompressedFiles; - { - AssignMediaCommand command = new AssignMediaCommand(); - command.FilesCompressed = compressed; - command.FileFacades = fileFacades; - command.Output = this.Output; - command.TableDefinitions = this.TableDefinitions; - command.Execute(); - - assignedMediaRows = command.MediaRows; - filesByCabinetMedia = command.FileFacadesByCabinetMedia; - uncompressedFiles = command.UncompressedFileFacades; - } - - // Update file sequence. - this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows); - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - - // Extended binder extensions can be called now that fields are resolved. - { - Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]); - - foreach (BinderExtension extension in this.Extensions) - { - extension.AfterResolvedFields(this.Output); - } - - List updatedFileFacades = new List(); - - foreach (Row updatedFile in updatedFiles.Rows) - { - string updatedId = updatedFile.FieldAsString(0); - - FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId)); - - updatedFileFacades.Add(updatedFacade); - } - - if (updatedFileFacades.Any()) - { - UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); - command.FileFacades = fileFacades; - command.UpdateFileFacades = updatedFileFacades; - command.ModularizationGuid = modularizationGuid; - command.Output = this.Output; - command.OverwriteHash = true; - command.TableDefinitions = this.TableDefinitions; - command.VariableCache = variableCache; - command.Execute(); - } - } - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - - Directory.CreateDirectory(this.TempFilesLocation); - - if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch) - { - CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand(); - command.FileFacades = fileFacades; - command.WixPatchIdTable = this.Output.Tables["WixPatchId"]; - command.TempFilesLocation = this.TempFilesLocation; - command.Execute(); - } - - // create cabinet files and process uncompressed files - string layoutDirectory = Path.GetDirectoryName(this.OutputPath); - if (!this.SuppressLayout || OutputType.Module == this.Output.Type) - { - Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles()); - - CreateCabinetsCommand command = new CreateCabinetsCommand(); - command.CabbingThreadCount = this.CabbingThreadCount; - command.DefaultCompressionLevel = this.DefaultCompressionLevel; - command.Output = this.Output; - command.FileManagers = this.FileManagers; - command.LayoutDirectory = layoutDirectory; - command.Compressed = compressed; - command.FileRowsByCabinet = filesByCabinetMedia; - command.ResolveMedia = this.ResolveMedia; - command.TableDefinitions = this.TableDefinitions; - command.TempFilesLocation = this.TempFilesLocation; - command.WixMediaTable = this.Output.Tables["WixMedia"]; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - } - - if (OutputType.Patch == this.Output.Type) - { - // copy output data back into the transforms - this.CopyToTransformData(this.Output); - } - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - - // add back suppressed tables which must be present prior to merging in modules - if (OutputType.Product == this.Output.Type) - { - Table wixMergeTable = this.Output.Tables["WixMerge"]; - - if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) - { - foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) - { - string sequenceTableName = sequence.ToString(); - Table sequenceTable = this.Output.Tables[sequenceTableName]; - - if (null == sequenceTable) - { - sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); - } - - if (0 == sequenceTable.Rows.Count) - { - suppressedTableNames.Add(sequenceTableName); - } - } - } - } - - foreach (BinderExtension extension in this.Extensions) - { - extension.Finish(this.Output); - } - - // generate database file - Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase()); - string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath)); - this.GenerateDatabase(this.Output, tempDatabaseFile, false, false); - - FileTransfer transfer; - if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future - { - transfer.Built = true; - fileTransfers.Add(transfer); - } - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - - // Output the output to a file - Pdb pdb = new Pdb(); - pdb.Output = this.Output; - if (!String.IsNullOrEmpty(this.PdbFile)) - { - pdb.Save(this.PdbFile); - } - - // Merge modules. - if (OutputType.Product == this.Output.Type) - { - Messaging.Instance.OnMessage(WixVerboses.MergingModules()); - - MergeModulesCommand command = new MergeModulesCommand(); - command.FileFacades = fileFacades; - command.Output = this.Output; - command.OutputPath = tempDatabaseFile; - command.SuppressedTableNames = suppressedTableNames; - command.Execute(); - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - } - - // inspect the MSI prior to running ICEs - InspectorCore inspectorCore = new InspectorCore(); - foreach (InspectorExtension inspectorExtension in this.InspectorExtensions) - { - inspectorExtension.Core = inspectorCore; - inspectorExtension.InspectDatabase(tempDatabaseFile, pdb); - - inspectorExtension.Core = null; // reset. - } - - if (Messaging.Instance.EncounteredError) - { - return; - } - - // validate the output if there is an MSI validator - if (null != this.Validator) - { - Stopwatch stopwatch = Stopwatch.StartNew(); - - // set the output file for source line information - this.Validator.Output = this.Output; - - Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase()); - - this.Validator.Validate(tempDatabaseFile); - - stopwatch.Stop(); - Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); - - // Stop processing if an error occurred. - if (Messaging.Instance.EncounteredError) - { - return; - } - } - - // Process uncompressed files. - if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) - { - ProcessUncompressedFilesCommand command = new ProcessUncompressedFilesCommand(); - command.Compressed = compressed; - command.FileFacades = uncompressedFiles; - command.LayoutDirectory = layoutDirectory; - command.LongNamesInImage = longNames; - command.MediaRows = assignedMediaRows; - command.ResolveMedia = this.ResolveMedia; - command.DatabasePath = tempDatabaseFile; - command.WixMediaTable = this.Output.Tables["WixMedia"]; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - } - - this.FileTransfers = fileTransfers; - this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList(); - } - - /// - /// Localize dialogs and controls. - /// - /// The tables to localize. - private void LocalizeUI(TableIndexedCollection tables) - { - Table dialogTable = tables["Dialog"]; - if (null != dialogTable) - { - foreach (Row row in dialogTable.Rows) - { - string dialog = (string)row[0]; - LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, null); - if (null != localizedControl) - { - if (CompilerConstants.IntegerNotSet != localizedControl.X) - { - row[1] = localizedControl.X; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Y) - { - row[2] = localizedControl.Y; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Width) - { - row[3] = localizedControl.Width; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Height) - { - row[4] = localizedControl.Height; - } - - row[5] = (int)row[5] | localizedControl.Attributes; - - if (!String.IsNullOrEmpty(localizedControl.Text)) - { - row[6] = localizedControl.Text; - } - } - } - } - - Table controlTable = tables["Control"]; - if (null != controlTable) - { - foreach (Row row in controlTable.Rows) - { - string dialog = (string)row[0]; - string control = (string)row[1]; - LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, control); - if (null != localizedControl) - { - if (CompilerConstants.IntegerNotSet != localizedControl.X) - { - row[3] = localizedControl.X.ToString(); - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Y) - { - row[4] = localizedControl.Y.ToString(); - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Width) - { - row[5] = localizedControl.Width.ToString(); - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Height) - { - row[6] = localizedControl.Height.ToString(); - } - - row[7] = (int)row[7] | localizedControl.Attributes; - - if (!String.IsNullOrEmpty(localizedControl.Text)) - { - row[9] = localizedControl.Text; - } - } - } - } - } - - /// - /// Copy file data between transform substorages and the patch output object - /// - /// The output to bind. - /// True if copying from transform to patch, false the other way. - private IEnumerable CopyFromTransformData(Output output) - { - CopyTransformDataCommand command = new CopyTransformDataCommand(); - command.CopyOutFileRows = true; - command.FileManagerCore = this.FileManagerCore; - command.FileManagers = this.FileManagers; - command.Output = output; - command.TableDefinitions = this.TableDefinitions; - command.Execute(); - - return command.FileFacades; - } - - /// - /// Copy file data between transform substorages and the patch output object - /// - /// The output to bind. - /// True if copying from transform to patch, false the other way. - private void CopyToTransformData(Output output) - { - CopyTransformDataCommand command = new CopyTransformDataCommand(); - command.CopyOutFileRows = false; - command.FileManagerCore = this.FileManagerCore; - command.FileManagers = this.FileManagers; - command.Output = output; - command.TableDefinitions = this.TableDefinitions; - command.Execute(); - } - - /// - /// Takes an id, and demodularizes it (if possible). - /// - /// - /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id. - /// - /// The type of the output to bind. - /// The modularization GUID. - /// The id to demodularize. - /// The demodularized id. - internal static string Demodularize(OutputType outputType, string modularizationGuid, string id) - { - if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal)) - { - id = id.Substring(0, id.Length - 37); - } - - return id; - } - - private void UpdateMediaSequences(OutputType outputType, IEnumerable fileFacades, RowDictionary mediaRows) - { - // Calculate sequence numbers and media disk id layout for all file media information objects. - if (OutputType.Module == outputType) - { - int lastSequence = 0; - foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. - { - facade.File.Sequence = ++lastSequence; - } - } - else - { - int lastSequence = 0; - MediaRow mediaRow = null; - Dictionary> patchGroups = new Dictionary>(); - - // sequence the non-patch-added files - foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. - { - if (null == mediaRow) - { - mediaRow = mediaRows.Get(facade.WixFile.DiskId); - if (OutputType.Patch == outputType) - { - // patch Media cannot start at zero - lastSequence = mediaRow.LastSequence; - } - } - else if (mediaRow.DiskId != facade.WixFile.DiskId) - { - mediaRow.LastSequence = lastSequence; - mediaRow = mediaRows.Get(facade.WixFile.DiskId); - } - - if (0 < facade.WixFile.PatchGroup) - { - List patchGroup = patchGroups[facade.WixFile.PatchGroup]; - - if (null == patchGroup) - { - patchGroup = new List(); - patchGroups.Add(facade.WixFile.PatchGroup, patchGroup); - } - - patchGroup.Add(facade); - } - else - { - facade.File.Sequence = ++lastSequence; - } - } - - if (null != mediaRow) - { - mediaRow.LastSequence = lastSequence; - mediaRow = null; - } - - // sequence the patch-added files - foreach (List patchGroup in patchGroups.Values) - { - foreach (FileFacade facade in patchGroup) - { - if (null == mediaRow) - { - mediaRow = mediaRows.Get(facade.WixFile.DiskId); - } - else if (mediaRow.DiskId != facade.WixFile.DiskId) - { - mediaRow.LastSequence = lastSequence; - mediaRow = mediaRows.Get(facade.WixFile.DiskId); - } - - facade.File.Sequence = ++lastSequence; - } - } - - if (null != mediaRow) - { - mediaRow.LastSequence = lastSequence; - } - } - } - - /// - /// Set the guids for components with generatable guids. - /// - /// Internal representation of the database to operate on. - private void SetComponentGuids(Output output) - { - Table componentTable = output.Tables["Component"]; - if (null != componentTable) - { - Hashtable registryKeyRows = null; - Hashtable directories = null; - Hashtable componentIdGenSeeds = null; - Dictionary> fileRows = null; - - // find components with generatable guids - foreach (ComponentRow componentRow in componentTable.Rows) - { - // component guid will be generated - if ("*" == componentRow.Guid) - { - if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath) - { - Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers)); - } - else if (componentRow.IsRegistryKeyPath) - { - if (null == registryKeyRows) - { - Table registryTable = output.Tables["Registry"]; - - registryKeyRows = new Hashtable(registryTable.Rows.Count); - - foreach (Row registryRow in registryTable.Rows) - { - registryKeyRows.Add((string)registryRow[0], registryRow); - } - } - - Row foundRow = registryKeyRows[componentRow.KeyPath] as Row; - - string bitness = componentRow.Is64Bit ? "64" : String.Empty; - if (null != foundRow) - { - string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]); - componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant(); - } - } - else // must be a File KeyPath - { - // if the directory table hasn't been loaded into an indexed hash - // of directory ids to target names do that now. - if (null == directories) - { - Table directoryTable = output.Tables["Directory"]; - - int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0; - - directories = new Hashtable(numDirectoryTableRows); - - // get the target paths for all directories - if (null != directoryTable) - { - foreach (Row row in directoryTable.Rows) - { - // if the directory Id already exists, we will skip it here since - // checking for duplicate primary keys is done later when importing tables - // into database - if (directories.ContainsKey(row[0])) - { - continue; - } - - string targetName = Installer.GetName((string)row[2], false, true); - directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName)); - } - } - } - - // if the component id generation seeds have not been indexed - // from the WixDirectory table do that now. - if (null == componentIdGenSeeds) - { - Table wixDirectoryTable = output.Tables["WixDirectory"]; - - int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0; - - componentIdGenSeeds = new Hashtable(numWixDirectoryRows); - - // if there are any WixDirectory rows, build up the Component Guid - // generation seeds indexed by Directory/@Id. - if (null != wixDirectoryTable) - { - foreach (Row row in wixDirectoryTable.Rows) - { - componentIdGenSeeds.Add(row[0], (string)row[1]); - } - } - } - - // if the file rows have not been indexed by File.Component yet - // then do that now - if (null == fileRows) - { - Table fileTable = output.Tables["File"]; - - int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0; - - fileRows = new Dictionary>(numFileRows); - - if (null != fileTable) - { - foreach (FileRow file in fileTable.Rows) - { - List files; - if (!fileRows.TryGetValue(file.Component, out files)) - { - files = new List(); - fileRows.Add(file.Component, files); - } - - files.Add(file); - } - } - } - - // validate component meets all the conditions to have a generated guid - List currentComponentFiles = fileRows[componentRow.Component]; - int numFilesInComponent = currentComponentFiles.Count; - string path = null; - - foreach (FileRow fileRow in currentComponentFiles) - { - if (fileRow.File == componentRow.KeyPath) - { - // calculate the key file's canonical target path - string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true); - string fileName = Installer.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture); - path = Path.Combine(directoryPath, fileName); - - // find paths that are not canonicalized - if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || - path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || - path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || - path.StartsWith("TARGETDIR", StringComparison.Ordinal) || - path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || - path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) - { - Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path)); - } - - // if component has more than one file, the key path must be versioned - if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) - { - Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers)); - } - } - else - { - // not a key path, so it must be an unversioned file if component has more than one file - if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) - { - Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers)); - } - } - } - - // if the rules were followed, reward with a generated guid - if (!Messaging.Instance.EncounteredError) - { - componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant(); - } - } - } - } - } - } - - /// - /// Creates instance transform substorages in the output. - /// - /// Output containing instance transform definitions. - private void CreateInstanceTransforms(Output output) - { - // Create and add substorages for instance transforms. - Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"]; - if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count) - { - string targetProductCode = null; - string targetUpgradeCode = null; - string targetProductVersion = null; - - Table targetSummaryInformationTable = output.Tables["_SummaryInformation"]; - Table targetPropertyTable = output.Tables["Property"]; - - // Get the data from target database - foreach (Row propertyRow in targetPropertyTable.Rows) - { - if ("ProductCode" == (string)propertyRow[0]) - { - targetProductCode = (string)propertyRow[1]; - } - else if ("ProductVersion" == (string)propertyRow[0]) - { - targetProductVersion = (string)propertyRow[1]; - } - else if ("UpgradeCode" == (string)propertyRow[0]) - { - targetUpgradeCode = (string)propertyRow[1]; - } - } - - // Index the Instance Component Rows. - Dictionary instanceComponentGuids = new Dictionary(); - Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"]; - if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count) - { - foreach (Row row in targetInstanceComponentTable.Rows) - { - // Build up all the instances, we'll get the Components rows from the real Component table. - instanceComponentGuids.Add((string)row[0], null); - } - - Table targetComponentTable = output.Tables["Component"]; - foreach (ComponentRow componentRow in targetComponentTable.Rows) - { - string component = (string)componentRow[0]; - if (instanceComponentGuids.ContainsKey(component)) - { - instanceComponentGuids[component] = componentRow; - } - } - } - - // Generate the instance transforms - foreach (Row instanceRow in wixInstanceTransformsTable.Rows) - { - string instanceId = (string)instanceRow[0]; - - Output instanceTransform = new Output(instanceRow.SourceLineNumbers); - instanceTransform.Type = OutputType.Transform; - instanceTransform.Codepage = output.Codepage; - - Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - string targetPlatformAndLanguage = null; - - foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows) - { - if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE - { - targetPlatformAndLanguage = (string)summaryInformationRow[1]; - } - - // Copy the row's data to the transform. - Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null); - copyOfSummaryRow[0] = summaryInformationRow[0]; - copyOfSummaryRow[1] = summaryInformationRow[1]; - } - - // Modify the appropriate properties. - Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); - - // Change the ProductCode property - string productCode = (string)instanceRow[2]; - if ("*" == productCode) - { - productCode = Common.GenerateGuid(); - } - - Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); - productCodeRow.Operation = RowOperation.Modify; - productCodeRow.Fields[1].Modified = true; - productCodeRow[0] = "ProductCode"; - productCodeRow[1] = productCode; - - // Change the instance property - Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); - instanceIdRow.Operation = RowOperation.Modify; - instanceIdRow.Fields[1].Modified = true; - instanceIdRow[0] = (string)instanceRow[1]; - instanceIdRow[1] = instanceId; - - if (null != instanceRow[3]) - { - // Change the ProductName property - Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); - productNameRow.Operation = RowOperation.Modify; - productNameRow.Fields[1].Modified = true; - productNameRow[0] = "ProductName"; - productNameRow[1] = (string)instanceRow[3]; - } - - if (null != instanceRow[4]) - { - // Change the UpgradeCode property - Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); - upgradeCodeRow.Operation = RowOperation.Modify; - upgradeCodeRow.Fields[1].Modified = true; - upgradeCodeRow[0] = "UpgradeCode"; - upgradeCodeRow[1] = instanceRow[4]; - - // Change the Upgrade table - Table targetUpgradeTable = output.Tables["Upgrade"]; - if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) - { - string upgradeId = (string)instanceRow[4]; - Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); - foreach (Row row in targetUpgradeTable.Rows) - { - // In case they are upgrading other codes to this new product, leave the ones that don't match the - // Product.UpgradeCode intact. - if (targetUpgradeCode == (string)row[0]) - { - Row upgradeRow = upgradeTable.CreateRow(null); - upgradeRow.Operation = RowOperation.Add; - upgradeRow.Fields[0].Modified = true; - // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. - // upgradeRow.Fields[0].PreviousData = (string)row[0]; - - // Inserting a new Upgrade record with the updated UpgradeCode - upgradeRow[0] = upgradeId; - upgradeRow[1] = row[1]; - upgradeRow[2] = row[2]; - upgradeRow[3] = row[3]; - upgradeRow[4] = row[4]; - upgradeRow[5] = row[5]; - upgradeRow[6] = row[6]; - - // Delete the old row - Row upgradeRemoveRow = upgradeTable.CreateRow(null); - upgradeRemoveRow.Operation = RowOperation.Delete; - upgradeRemoveRow[0] = row[0]; - upgradeRemoveRow[1] = row[1]; - upgradeRemoveRow[2] = row[2]; - upgradeRemoveRow[3] = row[3]; - upgradeRemoveRow[4] = row[4]; - upgradeRemoveRow[5] = row[5]; - upgradeRemoveRow[6] = row[6]; - } - } - } - } - - // If there are instance Components generate new GUIDs for them. - if (0 < instanceComponentGuids.Count) - { - Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); - foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values) - { - string guid = targetComponentRow.Guid; - if (!String.IsNullOrEmpty(guid)) - { - Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); - instanceComponentRow.Operation = RowOperation.Modify; - instanceComponentRow.Fields[1].Modified = true; - instanceComponentRow[0] = targetComponentRow[0]; - instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture); - instanceComponentRow[2] = targetComponentRow[2]; - instanceComponentRow[3] = targetComponentRow[3]; - instanceComponentRow[4] = targetComponentRow[4]; - instanceComponentRow[5] = targetComponentRow[5]; - } - } - } - - // Update the summary information - Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count); - foreach (Row row in instanceSummaryInformationTable.Rows) - { - summaryRows[row[0]] = row; - - if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) - { - row[1] = targetPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) - { - row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); - } - else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) - { - row[1] = 0; - } - else if ((int)SummaryInformation.Transform.Security == (int)row[0]) - { - row[1] = "4"; - } - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) - { - Row summaryRow = instanceSummaryInformationTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; - summaryRow[1] = targetPlatformAndLanguage; - } - else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) - { - Row summaryRow = instanceSummaryInformationTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - summaryRow[1] = "0"; - } - else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) - { - Row summaryRow = instanceSummaryInformationTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.Security; - summaryRow[1] = "4"; - } - - output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); - } - } - } - - /// - /// Validate that there are no duplicate GUIDs in the output. - /// - /// - /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a - /// warning, as the conditions might be mutually exclusive. - /// - private void ValidateComponentGuids(Output output) - { - Table componentTable = output.Tables["Component"]; - if (null != componentTable) - { - Dictionary componentGuidConditions = new Dictionary(componentTable.Rows.Count); - - foreach (ComponentRow row in componentTable.Rows) - { - // we don't care about unmanaged components and if there's a * GUID remaining, - // there's already an error that prevented it from being replaced with a real GUID. - if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid) - { - bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); - bool allComponentsHaveConditions = thisComponentHasCondition; - - if (componentGuidConditions.ContainsKey(row.Guid)) - { - allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition; - - if (allComponentsHaveConditions) - { - Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid)); - } - else - { - Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid)); - } - } - - componentGuidConditions[row.Guid] = allComponentsHaveConditions; - } - } - } - } - - /// - /// Update Control and BBControl text by reading from files when necessary. - /// - /// Internal representation of the msi database to operate upon. - private void UpdateControlText(Output output) - { - UpdateControlTextCommand command = new UpdateControlTextCommand(); - command.BBControlTable = output.Tables["BBControl"]; - command.WixBBControlTable = output.Tables["WixBBControl"]; - command.ControlTable = output.Tables["Control"]; - command.WixControlTable = output.Tables["WixControl"]; - command.Execute(); - } - - private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory) - { - string layout = null; - - foreach (IBinderFileManager fileManager in this.FileManagers) - { - layout = fileManager.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory); - if (!String.IsNullOrEmpty(layout)) - { - break; - } - } - - // If no binder file manager resolved the layout, do the default behavior. - if (String.IsNullOrEmpty(layout)) - { - if (String.IsNullOrEmpty(mediaLayoutDirectory)) - { - layout = layoutDirectory; - } - else if (Path.IsPathRooted(mediaLayoutDirectory)) - { - layout = mediaLayoutDirectory; - } - else - { - layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); - } - } - - return layout; - } - - /// - /// Creates the MSI/MSM/PCP database. - /// - /// Output to create database for. - /// The database file to create. - /// Whether to keep columns added in a transform. - /// Whether to use a subdirectory based on the file name for intermediate files. - private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) - { - GenerateDatabaseCommand command = new GenerateDatabaseCommand(); - command.Extensions = this.Extensions; - command.FileManagers = this.FileManagers; - command.Output = output; - command.OutputPath = databaseFile; - command.KeepAddedColumns = keepAddedColumns; - command.UseSubDirectory = useSubdirectory; - command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; - command.TableDefinitions = this.TableDefinitions; - command.TempFilesLocation = this.TempFilesLocation; - command.Codepage = this.Codepage; - command.Execute(); - } - } -} diff --git a/src/WixToolset.Core/Bind/BindTransformCommand.cs b/src/WixToolset.Core/Bind/BindTransformCommand.cs deleted file mode 100644 index e909f191..00000000 --- a/src/WixToolset.Core/Bind/BindTransformCommand.cs +++ /dev/null @@ -1,473 +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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Msi; - using WixToolset.Core.Native; - - internal class BindTransformCommand : ICommand - { - public IEnumerable Extensions { private get; set; } - - public IEnumerable FileManagers { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public string TempFilesLocation { private get; set; } - - public Output Transform { private get; set; } - - public string OutputPath { private get; set; } - - public void Execute() - { - int transformFlags = 0; - - Output targetOutput = new Output(null); - Output updatedOutput = new Output(null); - - // TODO: handle added columns - - // to generate a localized transform, both the target and updated - // databases need to have the same code page. the only reason to - // set different code pages is to support localized primary key - // columns, but that would only support deleting rows. if this - // becomes necessary, define a PreviousCodepage property on the - // Output class and persist this throughout transform generation. - targetOutput.Codepage = this.Transform.Codepage; - updatedOutput.Codepage = this.Transform.Codepage; - - // remove certain Property rows which will be populated from summary information values - string targetUpgradeCode = null; - string updatedUpgradeCode = null; - - Table propertyTable = this.Transform.Tables["Property"]; - if (null != propertyTable) - { - for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) - { - Row row = propertyTable.Rows[i]; - - if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) - { - propertyTable.Rows.RemoveAt(i); - - if ("UpgradeCode" == (string)row[0]) - { - updatedUpgradeCode = (string)row[1]; - } - } - } - } - - Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); - Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); - - // process special summary information values - foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows) - { - if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) - { - // convert from a web name if provided - string codePage = (string)row.Fields[1].Data; - if (null == codePage) - { - codePage = "0"; - } - else - { - codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); - } - - string previousCodePage = (string)row.Fields[1].PreviousData; - if (null == previousCodePage) - { - previousCodePage = "0"; - } - else - { - previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); - } - - Row targetCodePageRow = targetSummaryInfo.CreateRow(null); - targetCodePageRow[0] = 1; // PID_CODEPAGE - targetCodePageRow[1] = previousCodePage; - - Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); - updatedCodePageRow[0] = 1; // PID_CODEPAGE - updatedCodePageRow[1] = codePage; - } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || - (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) - { - // the target language - string[] propertyData = ((string)row[1]).Split(';'); - string lang = 2 == propertyData.Length ? propertyData[1] : "0"; - - Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; - Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; - - Row productLanguageRow = tempPropertyTable.CreateRow(null); - productLanguageRow[0] = "ProductLanguage"; - productLanguageRow[1] = lang; - - // set the platform;language on the MSI to be generated - Row templateRow = tempSummaryInfo.CreateRow(null); - templateRow[0] = 7; // PID_TEMPLATE - templateRow[1] = (string)row[1]; - } - else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) - { - string[] propertyData = ((string)row[1]).Split(';'); - - Row targetProductCodeRow = targetPropertyTable.CreateRow(null); - targetProductCodeRow[0] = "ProductCode"; - targetProductCodeRow[1] = propertyData[0].Substring(0, 38); - - Row targetProductVersionRow = targetPropertyTable.CreateRow(null); - targetProductVersionRow[0] = "ProductVersion"; - targetProductVersionRow[1] = propertyData[0].Substring(38); - - Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); - updatedProductCodeRow[0] = "ProductCode"; - updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); - - Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); - updatedProductVersionRow[0] = "ProductVersion"; - updatedProductVersionRow[1] = propertyData[1].Substring(38); - - // UpgradeCode is optional and may not exists in the target - // or upgraded databases, so do not include a null-valued - // UpgradeCode property. - - targetUpgradeCode = propertyData[2]; - if (!String.IsNullOrEmpty(targetUpgradeCode)) - { - Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); - targetUpgradeCodeRow[0] = "UpgradeCode"; - targetUpgradeCodeRow[1] = targetUpgradeCode; - - // If the target UpgradeCode is specified, an updated - // UpgradeCode is required. - if (String.IsNullOrEmpty(updatedUpgradeCode)) - { - updatedUpgradeCode = targetUpgradeCode; - } - } - - if (!String.IsNullOrEmpty(updatedUpgradeCode)) - { - Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); - updatedUpgradeCodeRow[0] = "UpgradeCode"; - updatedUpgradeCodeRow[1] = updatedUpgradeCode; - } - } - else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) - { - transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - } - else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) - { - // PID_LASTPRINTED should be null for transforms - row.Operation = RowOperation.None; - } - else - { - // add everything else as is - Row targetRow = targetSummaryInfo.CreateRow(null); - targetRow[0] = row[0]; - targetRow[1] = row[1]; - - Row updatedRow = updatedSummaryInfo.CreateRow(null); - updatedRow[0] = row[0]; - updatedRow[1] = row[1]; - } - } - - // Validate that both databases have an UpgradeCode if the - // authoring transform will validate the UpgradeCode; otherwise, - // MsiCreateTransformSummaryinfo() will fail with 1620. - if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && - (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) - { - Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired()); - } - - string emptyFile = null; - - foreach (Table table in this.Transform.Tables) - { - // Ignore unreal tables when building transforms except the _Stream table. - // These tables are ignored when generating the database so there is no reason - // to process them here. - if (table.Definition.Unreal && "_Streams" != table.Name) - { - continue; - } - - // process table operations - switch (table.Operation) - { - case TableOperation.Add: - updatedOutput.EnsureTable(table.Definition); - break; - case TableOperation.Drop: - targetOutput.EnsureTable(table.Definition); - continue; - default: - targetOutput.EnsureTable(table.Definition); - updatedOutput.EnsureTable(table.Definition); - break; - } - - // process row operations - foreach (Row row in table.Rows) - { - switch (row.Operation) - { - case RowOperation.Add: - Table updatedTable = updatedOutput.EnsureTable(table.Definition); - updatedTable.Rows.Add(row); - continue; - case RowOperation.Delete: - Table targetTable = targetOutput.EnsureTable(table.Definition); - targetTable.Rows.Add(row); - - // fill-in non-primary key values - foreach (Field field in row.Fields) - { - if (!field.Column.PrimaryKey) - { - if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) - { - field.Data = field.Column.MinValue; - } - else if (ColumnType.Object == field.Column.Type) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.TempFilesLocation, "empty"); - } - - field.Data = emptyFile; - } - else - { - field.Data = "0"; - } - } - } - continue; - } - - // Assure that the file table's sequence is populated - if ("File" == table.Name) - { - foreach (Row fileRow in table.Rows) - { - if (null == fileRow[7]) - { - if (RowOperation.Add == fileRow.Operation) - { - Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); - break; - } - - // Set to 1 to prevent invalid IDT file from being generated - fileRow[7] = 1; - } - } - } - - // process modified and unmodified rows - bool modifiedRow = false; - Row targetRow = new Row(null, table.Definition); - Row updatedRow = row; - for (int i = 0; i < row.Fields.Length; i++) - { - Field updatedField = row.Fields[i]; - - if (updatedField.Modified) - { - // set a different value in the target row to ensure this value will be modified during transform generation - if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) - { - if (null == updatedField.Data || 1 != (int)updatedField.Data) - { - targetRow[i] = 1; - } - else - { - targetRow[i] = 2; - } - } - else if (ColumnType.Object == updatedField.Column.Type) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.TempFilesLocation, "empty"); - } - - targetRow[i] = emptyFile; - } - else - { - if ("0" != (string)updatedField.Data) - { - targetRow[i] = "0"; - } - else - { - targetRow[i] = "1"; - } - } - - modifiedRow = true; - } - else if (ColumnType.Object == updatedField.Column.Type) - { - ObjectField objectField = (ObjectField)updatedField; - - // create an empty file for comparing against - if (null == objectField.PreviousData) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.TempFilesLocation, "empty"); - } - - targetRow[i] = emptyFile; - modifiedRow = true; - } - else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data)) - { - targetRow[i] = objectField.PreviousData; - modifiedRow = true; - } - } - else // unmodified - { - if (null != updatedField.Data) - { - targetRow[i] = updatedField.Data; - } - } - } - - // modified rows and certain special rows go in the target and updated msi databases - if (modifiedRow || - ("Property" == table.Name && - ("ProductCode" == (string)row[0] || - "ProductLanguage" == (string)row[0] || - "ProductVersion" == (string)row[0] || - "UpgradeCode" == (string)row[0]))) - { - Table targetTable = targetOutput.EnsureTable(table.Definition); - targetTable.Rows.Add(targetRow); - - Table updatedTable = updatedOutput.EnsureTable(table.Definition); - updatedTable.Rows.Add(updatedRow); - } - } - } - - foreach (BinderExtension extension in this.Extensions) - { - extension.Finish(this.Transform); - } - - // Any errors encountered up to this point can cause errors during generation. - if (Messaging.Instance.EncounteredError) - { - return; - } - - string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); - string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); - string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); - - try - { - if (!String.IsNullOrEmpty(emptyFile)) - { - using (FileStream fileStream = File.Create(emptyFile)) - { - } - } - - this.GenerateDatabase(targetOutput, targetDatabaseFile, false); - this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true); - - // make sure the directory exists - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); - - // create the transform file - using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) - { - using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) - { - if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) - { - updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); - } - else - { - Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); - } - } - } - } - finally - { - if (!String.IsNullOrEmpty(emptyFile)) - { - File.Delete(emptyFile); - } - } - } - - private bool CompareFiles(string targetFile, string updatedFile) - { - bool? compared = null; - foreach (IBinderFileManager fileManager in this.FileManagers) - { - compared = fileManager.CompareFiles(targetFile, updatedFile); - if (compared.HasValue) - { - break; - } - } - - if (!compared.HasValue) - { - throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. - } - - return compared.Value; - } - - private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns) - { - GenerateDatabaseCommand command = new GenerateDatabaseCommand(); - command.Codepage = output.Codepage; - command.Extensions = this.Extensions; - command.FileManagers = this.FileManagers; - command.KeepAddedColumns = keepAddedColumns; - command.Output = output; - command.OutputPath = outputPath; - command.TableDefinitions = this.TableDefinitions; - command.TempFilesLocation = this.TempFilesLocation; - command.SuppressAddingValidationRows = true; - command.UseSubDirectory = true; - command.Execute(); - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs deleted file mode 100644 index eb02a983..00000000 --- a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs +++ /dev/null @@ -1,112 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class AutomaticallySlipstreamPatchesCommand : ICommand - { - public IEnumerable PackageFacades { private get; set; } - - public Table WixBundlePatchTargetCodeTable { private get; set; } - - public Table SlipstreamMspTable { private get; set; } - - public void Execute() - { - List msiPackages = new List(); - Dictionary> targetsProductCode = new Dictionary>(); - Dictionary> targetsUpgradeCode = new Dictionary>(); - - foreach (PackageFacade facade in this.PackageFacades) - { - if (WixBundlePackageType.Msi == facade.Package.Type) - { - // Keep track of all MSI packages. - msiPackages.Add(facade.MsiPackage); - } - else if (WixBundlePackageType.Msp == facade.Package.Type && facade.MspPackage.Slipstream) - { - IEnumerable patchTargetCodeRows = this.WixBundlePatchTargetCodeTable.RowsAs().Where(r => r.MspPackageId == facade.Package.WixChainItemId); - - // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. - foreach (WixBundlePatchTargetCodeRow row in patchTargetCodeRows) - { - if (row.TargetsProductCode) - { - List rows; - if (!targetsProductCode.TryGetValue(row.TargetCode, out rows)) - { - rows = new List(); - targetsProductCode.Add(row.TargetCode, rows); - } - - rows.Add(row); - } - else if (row.TargetsUpgradeCode) - { - List rows; - if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows)) - { - rows = new List(); - targetsUpgradeCode.Add(row.TargetCode, rows); - } - } - } - } - } - - RowIndexedList slipstreamMspRows = new RowIndexedList(SlipstreamMspTable); - - // Loop through the MSI and slipstream patches targeting it. - foreach (WixBundleMsiPackageRow msi in msiPackages) - { - List rows; - if (targetsProductCode.TryGetValue(msi.ProductCode, out rows)) - { - foreach (WixBundlePatchTargetCodeRow row in rows) - { - Debug.Assert(row.TargetsProductCode); - Debug.Assert(!row.TargetsUpgradeCode); - - Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); - slipstreamMspRow[0] = msi.ChainPackageId; - slipstreamMspRow[1] = row.MspPackageId; - - if (slipstreamMspRows.TryAdd(slipstreamMspRow)) - { - SlipstreamMspTable.Rows.Add(slipstreamMspRow); - } - } - - rows = null; - } - - if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows)) - { - foreach (WixBundlePatchTargetCodeRow row in rows) - { - Debug.Assert(!row.TargetsProductCode); - Debug.Assert(row.TargetsUpgradeCode); - - Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); - slipstreamMspRow[0] = msi.ChainPackageId; - slipstreamMspRow[1] = row.MspPackageId; - - if (slipstreamMspRows.TryAdd(slipstreamMspRow)) - { - SlipstreamMspTable.Rows.Add(slipstreamMspRow); - } - } - - rows = null; - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs b/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs deleted file mode 100644 index 8cb07791..00000000 --- a/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs +++ /dev/null @@ -1,378 +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.Bind.Bundles -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Data; - - /// - /// Common functionality for Burn PE Writer & Reader for the WiX toolset. - /// - /// This class encapsulates common functionality related to - /// bundled/chained setup packages. - /// - /// - internal abstract class BurnCommon : IDisposable - { - public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; - public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; - public const string BurnUXContainerPayloadIdFormat = "p{0}"; - public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; - - // See WinNT.h for details about the PE format, including the - // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, - // IMAGE_FILE_HEADER, etc. - protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64; - protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0; - protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60; - - protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20) - protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0; - protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6; - protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20; - - protected const UInt32 IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER. - protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); - - protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20; - - protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs. - protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4; - protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; - - protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D; - protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550; - protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword. - - // The ".wixburn" section contains: - // 0- 3: magic number - // 4- 7: version - // 8-23: bundle GUID - // 24-27: engine (stub) size - // 28-31: original checksum - // 32-35: original signature offset - // 36-39: original signature size - // 40-43: container type (1 = CAB) - // 44-47: container count - // 48-51: byte count of manifest + UX container - // 52-55: byte count of attached container - protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0; - protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4; - protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8; - protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24; - protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28; - protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32; - protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36; - protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40; - protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44; - protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48; - protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52; - protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD) - - protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300; - protected const UInt32 BURN_SECTION_VERSION = 0x00000002; - - protected string fileExe; - protected UInt32 peOffset = UInt32.MaxValue; - protected UInt16 sections = UInt16.MaxValue; - protected UInt32 firstSectionOffset = UInt32.MaxValue; - protected UInt32 checksumOffset; - protected UInt32 certificateTableSignatureOffset; - protected UInt32 certificateTableSignatureSize; - protected UInt32 wixburnDataOffset = UInt32.MaxValue; - - // TODO: does this enum exist in another form somewhere? - /// - /// The types of attached containers that BurnWriter supports. - /// - public enum Container - { - Nothing = 0, - UX, - Attached - } - - /// - /// Creates a BurnCommon for re-writing a PE file. - /// - /// File to modify in-place. - /// GUID for the bundle. - public BurnCommon(string fileExe) - { - this.fileExe = fileExe; - } - - public UInt32 Checksum { get; protected set; } - public UInt32 SignatureOffset { get; protected set; } - public UInt32 SignatureSize { get; protected set; } - public UInt32 Version { get; protected set; } - public UInt32 StubSize { get; protected set; } - public UInt32 OriginalChecksum { get; protected set; } - public UInt32 OriginalSignatureOffset { get; protected set; } - public UInt32 OriginalSignatureSize { get; protected set; } - public UInt32 EngineSize { get; protected set; } - public UInt32 ContainerCount { get; protected set; } - public UInt32 UXAddress { get; protected set; } - public UInt32 UXSize { get; protected set; } - public UInt32 AttachedContainerAddress { get; protected set; } - public UInt32 AttachedContainerSize { get; protected set; } - - public void Dispose() - { - Dispose(true); - - GC.SuppressFinalize(this); - } - - /// - /// Copies one stream to another. - /// - /// Input stream. - /// Output stream. - /// Optional count of bytes to copy. 0 indicates whole input stream from current should be copied. - protected static int CopyStream(Stream input, Stream output, int size) - { - byte[] bytes = new byte[4096]; - int total = 0; - int read = 0; - do - { - read = Math.Min(bytes.Length, size - total); - read = input.Read(bytes, 0, read); - if (0 == read) - { - break; - } - - output.Write(bytes, 0, read); - total += read; - } while (0 == size || total < size); - - return total; - } - - /// - /// Initialize the common information about a Burn engine. - /// - /// Binary reader open against a Burn engine. - /// True if initialized. - protected bool Initialize(BinaryReader reader) - { - if (!GetWixburnSectionInfo(reader)) - { - return false; - } - - reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin); - byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE); - UInt32 uint32 = 0; - - uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC); - if (BURN_SECTION_MAGIC != uint32) - { - Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); - return false; - } - - this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); - if (BURN_SECTION_VERSION != this.Version) - { - Messaging.Instance.OnMessage(WixErrors.BundleTooNew(this.fileExe, this.Version)); - return false; - } - - uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now - if (1 != uint32) - { - Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); - return false; - } - - this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE); - this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM); - this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET); - this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE); - - this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT); - this.UXAddress = this.StubSize; - this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE); - - // If there is an original signature use that to determine the engine size. - if (0 < this.OriginalSignatureOffset) - { - this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize; - } - else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature. - { - this.EngineSize = this.SignatureOffset + this.SignatureSize; - } - else // just use the stub and UX container as the size of the engine. - { - this.EngineSize = this.StubSize + this.UXSize; - } - - this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0; - this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0; - - return true; - } - - protected virtual void Dispose(bool disposing) - { - } - - /// - /// Finds the ".wixburn" section in the current exe. - /// - /// true if the ".wixburn" section is successfully found; false otherwise - private bool GetWixburnSectionInfo(BinaryReader reader) - { - if (UInt32.MaxValue == this.wixburnDataOffset) - { - if (!EnsureNTHeader(reader)) - { - return false; - } - - UInt32 wixburnSectionOffset = UInt32.MaxValue; - byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE]; - - reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin); - for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex) - { - reader.Read(bytes, 0, bytes.Length); - - if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME)) - { - wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex); - break; - } - } - - if (UInt32.MaxValue == wixburnSectionOffset) - { - Messaging.Instance.OnMessage(WixErrors.StubMissingWixburnSection(this.fileExe)); - return false; - } - - // we need 56 bytes for the manifest header, which is always going to fit in - // the smallest alignment (512 bytes), but just to be paranoid... - if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA)) - { - Messaging.Instance.OnMessage(WixErrors.StubWixburnSectionTooSmall(this.fileExe)); - return false; - } - - this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA); - } - - return true; - } - - /// - /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe. - /// - /// true if the exe is a Windows executable; false otherwise - private bool EnsureNTHeader(BinaryReader reader) - { - if (UInt32.MaxValue == this.firstSectionOffset) - { - if (!EnsureDosHeader(reader)) - { - return false; - } - - reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin); - byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE); - - // Verify the NT signature... - if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE)) - { - Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); - return false; - } - - ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER); - - this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS); - this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader; - - this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM; - this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE; - this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset. - - bytes = reader.ReadBytes(sizeOptionalHeader); - this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM); - this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE); - this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4); - } - - return true; - } - - /// - /// Checks for a valid DOS header in the current exe. - /// - /// true if the exe starts with a DOS stub; false otherwise - private bool EnsureDosHeader(BinaryReader reader) - { - if (UInt32.MaxValue == this.peOffset) - { - byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE); - - // Verify the DOS 'MZ' signature. - if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC)) - { - Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); - return false; - } - - this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER); - } - - return true; - } - - /// - /// Reads a UInt16 value in little-endian format from an offset in an array of bytes. - /// - /// Array from which to read. - /// Beginning offset from which to read. - /// value at offset - private static UInt16 ReadUInt16(byte[] bytes, UInt32 offset) - { - Debug.Assert(offset + 2 <= bytes.Length); - return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8)); - } - - /// - /// Reads a UInt32 value in little-endian format from an offset in an array of bytes. - /// - /// Array from which to read. - /// Beginning offset from which to read. - /// value at offset - private static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) - { - Debug.Assert(offset + 4 <= bytes.Length); - return (UInt32)(bytes[offset] + (bytes[offset + 1] << 8) + (bytes[offset + 2] << 16) + (bytes[offset + 3] << 24)); - } - - /// - /// Reads a UInt64 value in little-endian format from an offset in an array of bytes. - /// - /// Array from which to read. - /// Beginning offset from which to read. - /// value at offset - private static UInt64 ReadUInt64(byte[] bytes, UInt32 offset) - { - Debug.Assert(offset + 8 <= bytes.Length); - return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)(BurnCommon.ReadUInt32(bytes, offset + 4)) << 32); - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/BurnReader.cs b/src/WixToolset.Core/Bind/Bundles/BurnReader.cs deleted file mode 100644 index f6d7a197..00000000 --- a/src/WixToolset.Core/Bind/Bundles/BurnReader.cs +++ /dev/null @@ -1,210 +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.Bind.Bundles -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Xml; - using WixToolset.Cab; - - /// - /// Burn PE reader for the WiX toolset. - /// - /// This class encapsulates reading from a stub EXE with containers attached - /// for dissecting bundled/chained setup packages. - /// - /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) - /// { - /// reader.ExtractUXContainer(file1, tempFolder); - /// } - /// - internal class BurnReader : BurnCommon - { - private bool disposed; - - private bool invalidBundle; - private BinaryReader binaryReader; - private List attachedContainerPayloadNames; - - /// - /// Creates a BurnReader for reading a PE file. - /// - /// File to read. - private BurnReader(string fileExe) - : base(fileExe) - { - this.attachedContainerPayloadNames = new List(); - } - - /// - /// Gets the underlying stream. - /// - public Stream Stream - { - get - { - return (null != this.binaryReader) ? this.binaryReader.BaseStream : null; - } - } - - /// - /// Opens a Burn reader. - /// - /// Path to file. - /// Burn reader. - public static BurnReader Open(string fileExe) - { - BurnReader reader = new BurnReader(fileExe); - - reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); - if (!reader.Initialize(reader.binaryReader)) - { - reader.invalidBundle = true; - } - - return reader; - } - - /// - /// Gets the UX container from the exe and extracts its contents to the output directory. - /// - /// Directory to write extracted files to. - /// True if successful, false otherwise - public bool ExtractUXContainer(string outputDirectory, string tempDirectory) - { - // No UX container to extract - if (this.UXAddress == 0 || this.UXSize == 0) - { - return false; - } - - if (this.invalidBundle) - { - return false; - } - - Directory.CreateDirectory(outputDirectory); - string tempCabPath = Path.Combine(tempDirectory, "ux.cab"); - string manifestOriginalPath = Path.Combine(outputDirectory, "0"); - string manifestPath = Path.Combine(outputDirectory, "manifest.xml"); - - this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); - using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) - { - BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize); - } - - using (WixExtractCab extract = new WixExtractCab()) - { - extract.Extract(tempCabPath, outputDirectory); - } - - Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); - File.Delete(manifestPath); - File.Move(manifestOriginalPath, manifestPath); - - XmlDocument document = new XmlDocument(); - document.Load(manifestPath); - XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace); - XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); - XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); - - foreach (XmlNode uxPayload in uxPayloads) - { - XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); - XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); - - string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); - string destinationPath = Path.Combine(outputDirectory, filePathNode.Value); - - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - File.Delete(destinationPath); - File.Move(sourcePath, destinationPath); - } - - foreach (XmlNode payload in payloads) - { - XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); - XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath"); - XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging"); - - string sourcePath = sourcePathNode.Value; - string destinationPath = filePathNode.Value; - string packaging = packagingNode.Value; - - if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) - { - this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); - } - } - - return true; - } - - /// - /// Gets the attached container from the exe and extracts its contents to the output directory. - /// - /// Directory to write extracted files to. - /// True if successful, false otherwise - public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory) - { - // No attached container to extract - if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0) - { - return false; - } - - if (this.invalidBundle) - { - return false; - } - - Directory.CreateDirectory(outputDirectory); - string tempCabPath = Path.Combine(tempDirectory, "attached.cab"); - - this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin); - using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) - { - BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize); - } - - using (WixExtractCab extract = new WixExtractCab()) - { - extract.Extract(tempCabPath, outputDirectory); - } - - foreach (DictionaryEntry entry in this.attachedContainerPayloadNames) - { - string sourcePath = Path.Combine(outputDirectory, (string)entry.Key); - string destinationPath = Path.Combine(outputDirectory, (string)entry.Value); - - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - File.Delete(destinationPath); - File.Move(sourcePath, destinationPath); - } - - return true; - } - - /// - /// Dispose object. - /// - /// True when releasing managed objects. - protected override void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing && this.binaryReader != null) - { - this.binaryReader.Close(); - this.binaryReader = null; - } - - this.disposed = true; - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs b/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs deleted file mode 100644 index bc0baf46..00000000 --- a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs +++ /dev/null @@ -1,239 +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.Bind.Bundles -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Data; - - /// - /// Burn PE writer for the WiX toolset. - /// - /// This class encapsulates reading/writing to a stub EXE for - /// creating bundled/chained setup packages. - /// - /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) - /// { - /// writer.AppendContainer(file1, BurnWriter.Container.UX); - /// writer.AppendContainer(file2, BurnWriter.Container.Attached); - /// } - /// - internal class BurnWriter : BurnCommon - { - private bool disposed; - private bool invalidBundle; - private BinaryWriter binaryWriter; - - /// - /// Creates a BurnWriter for re-writing a PE file. - /// - /// File to modify in-place. - /// GUID for the bundle. - private BurnWriter(string fileExe) - : base(fileExe) - { - } - - /// - /// Opens a Burn writer. - /// - /// Path to file. - /// Burn writer. - public static BurnWriter Open(string fileExe) - { - BurnWriter writer = new BurnWriter(fileExe); - - using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) - { - if (!writer.Initialize(binaryReader)) - { - writer.invalidBundle = true; - } - } - - if (!writer.invalidBundle) - { - writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); - } - - return writer; - } - - /// - /// Update the ".wixburn" section data. - /// - /// Size of the stub engine "burn.exe". - /// Unique identifier for this bundle. - /// - public bool InitializeBundleSectionData(long stubSize, Guid bundleId) - { - if (this.invalidBundle) - { - return false; - } - - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); - - Messaging.Instance.OnMessage(WixVerboses.BundleGuid(bundleId.ToString("B"))); - this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); - this.binaryWriter.Write(bundleId.ToByteArray()); - - this.StubSize = (uint)stubSize; - - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0); - this.binaryWriter.BaseStream.Flush(); - - this.EngineSize = this.StubSize; - - return true; - } - - /// - /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. - /// - /// File path to append to the current exe. - /// Container section represented by the fileContainer. - /// true if the container data is successfully appended; false otherwise - public bool AppendContainer(string fileContainer, BurnCommon.Container container) - { - using (FileStream reader = File.OpenRead(fileContainer)) - { - return this.AppendContainer(reader, reader.Length, container); - } - } - - /// - /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. - /// - /// File stream to append to the current exe. - /// Size of container to append. - /// Container section represented by the fileContainer. - /// true if the container data is successfully appended; false otherwise - public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) - { - UInt32 burnSectionCount = 0; - UInt32 burnSectionOffsetSize = 0; - - switch (container) - { - case Container.UX: - burnSectionCount = 1; - burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE; - // TODO: verify that the size in the section data is 0 or the same size. - this.EngineSize += (uint)containerSize; - this.UXSize = (uint)containerSize; - break; - - case Container.Attached: - burnSectionCount = 2; - burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE; - // TODO: verify that the size in the section data is 0 or the same size. - this.AttachedContainerSize = (uint)containerSize; - break; - - default: - Debug.Assert(false); - return false; - } - - return AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); - } - - public void RememberThenResetSignature() - { - if (this.invalidBundle) - { - return; - } - - this.OriginalChecksum = this.Checksum; - this.OriginalSignatureOffset = this.SignatureOffset; - this.OriginalSignatureSize = this.SignatureSize; - - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); - - this.Checksum = 0; - this.SignatureOffset = 0; - this.SignatureSize = 0; - - this.WriteToOffset(this.checksumOffset, this.Checksum); - this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); - this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); - } - - /// - /// Dispose object. - /// - /// True when releasing managed objects. - protected override void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing && this.binaryWriter != null) - { - this.binaryWriter.Close(); - this.binaryWriter = null; - } - - this.disposed = true; - } - } - - /// - /// Appends a container to the exe and updates the ".wixburn" section data to point to it. - /// - /// File stream to append to the current exe. - /// Offset of size field for this container in ".wixburn" section data. - /// true if the container data is successfully appended; false otherwise - private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount) - { - if (this.invalidBundle) - { - return false; - } - - // Update the ".wixburn" section data - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); - this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); - - // Append the container to the end of the existing bits. - this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); - BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); - this.binaryWriter.BaseStream.Flush(); - - return true; - } - - /// - /// Writes the value to an offset in the Burn section data. - /// - /// Offset in to the Burn section data. - /// Value to write. - private void WriteToBurnSectionOffset(uint offset, uint value) - { - this.WriteToOffset(this.wixburnDataOffset + offset, value); - } - - /// - /// Writes the value to an offset in the Burn stub. - /// - /// Offset in to the Burn stub. - /// Value to write. - private void WriteToOffset(uint offset, uint value) - { - this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); - this.binaryWriter.Write(value); - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs deleted file mode 100644 index 1040b394..00000000 --- a/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ /dev/null @@ -1,241 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class CreateBootstrapperApplicationManifestCommand : ICommand - { - public WixBundleRow BundleRow { private get; set; } - - public IEnumerable ChainPackages { private get; set; } - - public int LastUXPayloadIndex { private get; set; } - - public IEnumerable MsiFeatures { private get; set; } - - public Output Output { private get; set; } - - public RowDictionary Payloads { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public string TempFilesLocation { private get; set; } - - public WixBundlePayloadRow BootstrapperApplicationManifestPayloadRow { get; private set; } - - public void Execute() - { - this.GenerateBAManifestBundleTables(); - - this.GenerateBAManifestMsiFeatureTables(); - - this.GenerateBAManifestPackageTables(); - - this.GenerateBAManifestPayloadTables(); - - string baManifestPath = Path.Combine(this.TempFilesLocation, "wix-badata.xml"); - - this.CreateBootstrapperApplicationManifest(baManifestPath); - - this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath); - } - - private void GenerateBAManifestBundleTables() - { - Table wixBundlePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleProperties"]); - - Row row = wixBundlePropertiesTable.CreateRow(this.BundleRow.SourceLineNumbers); - row[0] = this.BundleRow.Name; - row[1] = this.BundleRow.LogPathVariable; - row[2] = (YesNoDefaultType.Yes == this.BundleRow.Compressed) ? "yes" : "no"; - row[3] = this.BundleRow.BundleId.ToString("B"); - row[4] = this.BundleRow.UpgradeCode; - row[5] = this.BundleRow.PerMachine ? "yes" : "no"; - } - - private void GenerateBAManifestPackageTables() - { - Table wixPackagePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageProperties"]); - - foreach (PackageFacade package in this.ChainPackages) - { - WixBundlePayloadRow packagePayload = this.Payloads[package.Package.PackagePayload]; - - Row row = wixPackagePropertiesTable.CreateRow(package.Package.SourceLineNumbers); - row[0] = package.Package.WixChainItemId; - row[1] = (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"; - row[2] = package.Package.DisplayName; - row[3] = package.Package.Description; - row[4] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?) - row[5] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed) - row[6] = package.Package.InstallSize.Value.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space) - row[7] = package.Package.Type.ToString(); - row[8] = package.Package.Permanent ? "yes" : "no"; - row[9] = package.Package.LogPathVariable; - row[10] = package.Package.RollbackLogPathVariable; - row[11] = (PackagingType.Embedded == packagePayload.Packaging) ? "yes" : "no"; - - if (WixBundlePackageType.Msi == package.Package.Type) - { - row[12] = package.MsiPackage.DisplayInternalUI ? "yes" : "no"; - - if (!String.IsNullOrEmpty(package.MsiPackage.ProductCode)) - { - row[13] = package.MsiPackage.ProductCode; - } - - if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) - { - row[14] = package.MsiPackage.UpgradeCode; - } - } - else if (WixBundlePackageType.Msp == package.Package.Type) - { - row[12] = package.MspPackage.DisplayInternalUI ? "yes" : "no"; - - if (!String.IsNullOrEmpty(package.MspPackage.PatchCode)) - { - row[13] = package.MspPackage.PatchCode; - } - } - - if (!String.IsNullOrEmpty(package.Package.Version)) - { - row[15] = package.Package.Version; - } - - if (!String.IsNullOrEmpty(package.Package.InstallCondition)) - { - row[16] = package.Package.InstallCondition; - } - - switch (package.Package.Cache) - { - case YesNoAlwaysType.No: - row[17] = "no"; - break; - case YesNoAlwaysType.Yes: - row[17] = "yes"; - break; - case YesNoAlwaysType.Always: - row[17] = "always"; - break; - } - } - } - - private void GenerateBAManifestMsiFeatureTables() - { - Table wixPackageFeatureInfoTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageFeatureInfo"]); - - foreach (WixBundleMsiFeatureRow feature in this.MsiFeatures) - { - Row row = wixPackageFeatureInfoTable.CreateRow(feature.SourceLineNumbers); - row[0] = feature.ChainPackageId; - row[1] = feature.Name; - row[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture); - row[3] = feature.Parent; - row[4] = feature.Title; - row[5] = feature.Description; - row[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture); - row[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture); - row[8] = feature.Directory; - row[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture); - } - - } - - private void GenerateBAManifestPayloadTables() - { - Table wixPayloadPropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPayloadProperties"]); - - foreach (WixBundlePayloadRow payload in this.Payloads.Values) - { - WixPayloadPropertiesRow row = (WixPayloadPropertiesRow)wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers); - row.Id = payload.Id; - row.Package = payload.Package; - row.Container = payload.Container; - row.Name = payload.Name; - row.Size = payload.FileSize.ToString(); - row.DownloadUrl = payload.DownloadUrl; - row.LayoutOnly = payload.LayoutOnly ? "yes" : "no"; - } - } - - private void CreateBootstrapperApplicationManifest(string path) - { - using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.Unicode)) - { - writer.Formatting = Formatting.Indented; - writer.WriteStartDocument(); - writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/2010/BootstrapperApplicationData"); - - foreach (Table table in this.Output.Tables) - { - if (table.Definition.BootstrapperApplicationData) - { - // We simply assert that the table (and field) name is valid, because - // this is up to the extension developer to get right. An author will - // only affect the attribute value, and that will get properly escaped. -#if DEBUG - Debug.Assert(Common.IsIdentifier(table.Name)); - foreach (ColumnDefinition column in table.Definition.Columns) - { - Debug.Assert(Common.IsIdentifier(column.Name)); - } -#endif // DEBUG - - foreach (Row row in table.Rows) - { - writer.WriteStartElement(table.Name); - - foreach (Field field in row.Fields) - { - if (null != field.Data) - { - writer.WriteAttributeString(field.Column.Name, field.Data.ToString()); - } - } - - writer.WriteEndElement(); - } - } - } - - writer.WriteEndElement(); - writer.WriteEndDocument(); - } - } - - private WixBundlePayloadRow CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) - { - Table payloadTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePayload"]); - WixBundlePayloadRow row = (WixBundlePayloadRow)payloadTable.CreateRow(this.BundleRow.SourceLineNumbers); - row.Id = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); - row.Name = "BootstrapperApplicationData.xml"; - row.SourceFile = baManifestPath; - row.Compressed = YesNoDefaultType.Yes; - row.UnresolvedSourceFile = baManifestPath; - row.Container = Compiler.BurnUXContainerId; - row.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex); - row.Packaging = PackagingType.Embedded; - - FileInfo fileInfo = new FileInfo(row.SourceFile); - - row.FileSize = (int)fileInfo.Length; - - row.Hash = Common.GetFileHash(fileInfo.FullName); - - return row; - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs deleted file mode 100644 index 7bc708a3..00000000 --- a/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs +++ /dev/null @@ -1,686 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - - internal class CreateBurnManifestCommand : ICommand - { - public IEnumerable FileManagers { private get; set; } - - public Output Output { private get; set; } - - public string ExecutableName { private get; set; } - - public WixBundleRow BundleInfo { private get; set; } - - public WixChainRow Chain { private get; set; } - - public string OutputPath { private get; set; } - - public IEnumerable RollbackBoundaries { private get; set; } - - public IEnumerable OrderedPackages { private get; set; } - - public IEnumerable OrderedSearches { private get; set; } - - public Dictionary Payloads { private get; set; } - - public Dictionary Containers { private get; set; } - - public IEnumerable UXContainerPayloads { private get; set; } - - public IEnumerable Catalogs { private get; set; } - - public void Execute() - { - using (XmlTextWriter writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) - { - writer.WriteStartDocument(); - - writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); - - // Write the condition, if there is one - if (null != this.BundleInfo.Condition) - { - writer.WriteElementString("Condition", this.BundleInfo.Condition); - } - - // Write the log element if default logging wasn't disabled. - if (!String.IsNullOrEmpty(this.BundleInfo.LogPrefix)) - { - writer.WriteStartElement("Log"); - if (!String.IsNullOrEmpty(this.BundleInfo.LogPathVariable)) - { - writer.WriteAttributeString("PathVariable", this.BundleInfo.LogPathVariable); - } - writer.WriteAttributeString("Prefix", this.BundleInfo.LogPrefix); - writer.WriteAttributeString("Extension", this.BundleInfo.LogExtension); - writer.WriteEndElement(); - } - - - // Get update if specified. - WixBundleUpdateRow updateRow = this.Output.Tables["WixBundleUpdate"].RowsAs().FirstOrDefault(); - - if (null != updateRow) - { - writer.WriteStartElement("Update"); - writer.WriteAttributeString("Location", updateRow.Location); - writer.WriteEndElement(); // - } - - // Write the RelatedBundle elements - - // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates - // enumeration in the index row list is not used). - RowIndexedList relatedBundles = new RowIndexedList(this.Output.Tables["WixRelatedBundle"]); - - foreach (WixRelatedBundleRow relatedBundle in relatedBundles) - { - writer.WriteStartElement("RelatedBundle"); - writer.WriteAttributeString("Id", relatedBundle.Id); - writer.WriteAttributeString("Action", Convert.ToString(relatedBundle.Action, CultureInfo.InvariantCulture)); - writer.WriteEndElement(); - } - - // Write the variables - IEnumerable variables = this.Output.Tables["WixBundleVariable"].RowsAs(); - - foreach (WixBundleVariableRow variable in variables) - { - writer.WriteStartElement("Variable"); - writer.WriteAttributeString("Id", variable.Id); - if (null != variable.Type) - { - writer.WriteAttributeString("Value", variable.Value); - writer.WriteAttributeString("Type", variable.Type); - } - writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); - writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); - writer.WriteEndElement(); - } - - // Write the searches - foreach (WixSearchInfo searchinfo in this.OrderedSearches) - { - searchinfo.WriteXml(writer); - } - - // write the UX element - writer.WriteStartElement("UX"); - if (!String.IsNullOrEmpty(this.BundleInfo.SplashScreenBitmapPath)) - { - writer.WriteAttributeString("SplashScreen", "yes"); - } - - // write the UX allPayloads... - foreach (WixBundlePayloadRow payload in this.UXContainerPayloads) - { - writer.WriteStartElement("Payload"); - this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); - writer.WriteEndElement(); - } - - writer.WriteEndElement(); // - - // write the catalog elements - if (this.Catalogs.Any()) - { - foreach (WixBundleCatalogRow catalog in this.Catalogs) - { - writer.WriteStartElement("Catalog"); - writer.WriteAttributeString("Id", catalog.Id); - writer.WriteAttributeString("Payload", catalog.Payload); - writer.WriteEndElement(); - } - } - - foreach (WixBundleContainerRow container in this.Containers.Values) - { - if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) - { - writer.WriteStartElement("Container"); - this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); - writer.WriteEndElement(); - } - } - - foreach (WixBundlePayloadRow payload in this.Payloads.Values) - { - if (PackagingType.Embedded == payload.Packaging && Compiler.BurnUXContainerId != payload.Container) - { - writer.WriteStartElement("Payload"); - this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); - writer.WriteEndElement(); - } - else if (PackagingType.External == payload.Packaging) - { - writer.WriteStartElement("Payload"); - this.WriteBurnManifestPayloadAttributes(writer, payload, false, this.Payloads); - writer.WriteEndElement(); - } - } - - foreach (WixBundleRollbackBoundaryRow rollbackBoundary in this.RollbackBoundaries) - { - writer.WriteStartElement("RollbackBoundary"); - writer.WriteAttributeString("Id", rollbackBoundary.ChainPackageId); - writer.WriteAttributeString("Vital", YesNoType.Yes == rollbackBoundary.Vital ? "yes" : "no"); - writer.WriteAttributeString("Transaction", YesNoType.Yes == rollbackBoundary.Transaction ? "yes" : "no"); - writer.WriteEndElement(); - } - - // Write the registration information... - writer.WriteStartElement("Registration"); - - writer.WriteAttributeString("Id", this.BundleInfo.BundleId.ToString("B")); - writer.WriteAttributeString("ExecutableName", this.ExecutableName); - writer.WriteAttributeString("PerMachine", this.BundleInfo.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Tag", this.BundleInfo.Tag); - writer.WriteAttributeString("Version", this.BundleInfo.Version); - writer.WriteAttributeString("ProviderKey", this.BundleInfo.ProviderKey); - - writer.WriteStartElement("Arp"); - writer.WriteAttributeString("Register", (0 < this.BundleInfo.DisableModify && this.BundleInfo.DisableRemove) ? "no" : "yes"); // do not register if disabled modify and remove. - writer.WriteAttributeString("DisplayName", this.BundleInfo.Name); - writer.WriteAttributeString("DisplayVersion", this.BundleInfo.Version); - - if (!String.IsNullOrEmpty(this.BundleInfo.Publisher)) - { - writer.WriteAttributeString("Publisher", this.BundleInfo.Publisher); - } - - if (!String.IsNullOrEmpty(this.BundleInfo.HelpLink)) - { - writer.WriteAttributeString("HelpLink", this.BundleInfo.HelpLink); - } - - if (!String.IsNullOrEmpty(this.BundleInfo.HelpTelephone)) - { - writer.WriteAttributeString("HelpTelephone", this.BundleInfo.HelpTelephone); - } - - if (!String.IsNullOrEmpty(this.BundleInfo.AboutUrl)) - { - writer.WriteAttributeString("AboutUrl", this.BundleInfo.AboutUrl); - } - - if (!String.IsNullOrEmpty(this.BundleInfo.UpdateUrl)) - { - writer.WriteAttributeString("UpdateUrl", this.BundleInfo.UpdateUrl); - } - - if (!String.IsNullOrEmpty(this.BundleInfo.ParentName)) - { - writer.WriteAttributeString("ParentDisplayName", this.BundleInfo.ParentName); - } - - if (1 == this.BundleInfo.DisableModify) - { - writer.WriteAttributeString("DisableModify", "yes"); - } - else if (2 == this.BundleInfo.DisableModify) - { - writer.WriteAttributeString("DisableModify", "button"); - } - - if (this.BundleInfo.DisableRemove) - { - writer.WriteAttributeString("DisableRemove", "yes"); - } - writer.WriteEndElement(); // - - // Get update registration if specified. - WixUpdateRegistrationRow updateRegistrationInfo = this.Output.Tables["WixUpdateRegistration"].RowsAs().FirstOrDefault(); - - if (null != updateRegistrationInfo) - { - writer.WriteStartElement("Update"); // - writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer); - - if (!String.IsNullOrEmpty(updateRegistrationInfo.Department)) - { - writer.WriteAttributeString("Department", updateRegistrationInfo.Department); - } - - if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily)) - { - writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily); - } - - writer.WriteAttributeString("Name", updateRegistrationInfo.Name); - writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification); - writer.WriteEndElement(); // - } - - IEnumerable bundleTags = this.Output.Tables["WixBundleTag"].RowsAs(); - - foreach (Row row in bundleTags) - { - writer.WriteStartElement("SoftwareTag"); - writer.WriteAttributeString("Filename", (string)row[0]); - writer.WriteAttributeString("Regid", (string)row[1]); - writer.WriteCData((string)row[4]); - writer.WriteEndElement(); - } - - writer.WriteEndElement(); // - - // write the Chain... - writer.WriteStartElement("Chain"); - if (this.Chain.DisableRollback) - { - writer.WriteAttributeString("DisableRollback", "yes"); - } - - if (this.Chain.DisableSystemRestore) - { - writer.WriteAttributeString("DisableSystemRestore", "yes"); - } - - if (this.Chain.ParallelCache) - { - writer.WriteAttributeString("ParallelCache", "yes"); - } - - // Index a few tables by package. - ILookup targetCodesByPatch = this.Output.Tables["WixBundlePatchTargetCode"].RowsAs().ToLookup(r => r.MspPackageId); - ILookup msiFeaturesByPackage = this.Output.Tables["WixBundleMsiFeature"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup msiPropertiesByPackage = this.Output.Tables["WixBundleMsiProperty"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup payloadsByPackage = this.Payloads.Values.ToLookup(p => p.Package); - ILookup relatedPackagesByPackage = this.Output.Tables["WixBundleRelatedPackage"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup slipstreamMspsByPackage = this.Output.Tables["WixBundleSlipstreamMsp"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup exitCodesByPackage = this.Output.Tables["WixBundlePackageExitCode"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup commandLinesByPackage = this.Output.Tables["WixBundlePackageCommandLine"].RowsAs().ToLookup(r => r.ChainPackageId); - - // Build up the list of target codes from all the MSPs in the chain. - List targetCodes = new List(); - - foreach (PackageFacade package in this.OrderedPackages) - { - writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.Package.Type)); - - writer.WriteAttributeString("Id", package.Package.WixChainItemId); - - switch (package.Package.Cache) - { - case YesNoAlwaysType.No: - writer.WriteAttributeString("Cache", "no"); - break; - case YesNoAlwaysType.Yes: - writer.WriteAttributeString("Cache", "yes"); - break; - case YesNoAlwaysType.Always: - writer.WriteAttributeString("Cache", "always"); - break; - } - - writer.WriteAttributeString("CacheId", package.Package.CacheId); - writer.WriteAttributeString("InstallSize", Convert.ToString(package.Package.InstallSize)); - writer.WriteAttributeString("Size", Convert.ToString(package.Package.Size)); - writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.Package.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Permanent", package.Package.Permanent ? "yes" : "no"); - writer.WriteAttributeString("Vital", (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"); - - if (null != package.Package.RollbackBoundary) - { - writer.WriteAttributeString("RollbackBoundaryForward", package.Package.RollbackBoundary); - } - - if (!String.IsNullOrEmpty(package.Package.RollbackBoundaryBackward)) - { - writer.WriteAttributeString("RollbackBoundaryBackward", package.Package.RollbackBoundaryBackward); - } - - if (!String.IsNullOrEmpty(package.Package.LogPathVariable)) - { - writer.WriteAttributeString("LogPathVariable", package.Package.LogPathVariable); - } - - if (!String.IsNullOrEmpty(package.Package.RollbackLogPathVariable)) - { - writer.WriteAttributeString("RollbackLogPathVariable", package.Package.RollbackLogPathVariable); - } - - if (!String.IsNullOrEmpty(package.Package.InstallCondition)) - { - writer.WriteAttributeString("InstallCondition", package.Package.InstallCondition); - } - - if (WixBundlePackageType.Exe == package.Package.Type) - { - writer.WriteAttributeString("DetectCondition", package.ExePackage.DetectCondition); - writer.WriteAttributeString("InstallArguments", package.ExePackage.InstallCommand); - writer.WriteAttributeString("UninstallArguments", package.ExePackage.UninstallCommand); - writer.WriteAttributeString("RepairArguments", package.ExePackage.RepairCommand); - writer.WriteAttributeString("Repairable", package.ExePackage.Repairable ? "yes" : "no"); - if (!String.IsNullOrEmpty(package.ExePackage.ExeProtocol)) - { - writer.WriteAttributeString("Protocol", package.ExePackage.ExeProtocol); - } - } - else if (WixBundlePackageType.Msi == package.Package.Type) - { - writer.WriteAttributeString("ProductCode", package.MsiPackage.ProductCode); - writer.WriteAttributeString("Language", package.MsiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Version", package.MsiPackage.ProductVersion); - writer.WriteAttributeString("DisplayInternalUI", package.MsiPackage.DisplayInternalUI ? "yes" : "no"); - if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", package.MsiPackage.UpgradeCode); - } - } - else if (WixBundlePackageType.Msp == package.Package.Type) - { - writer.WriteAttributeString("PatchCode", package.MspPackage.PatchCode); - writer.WriteAttributeString("PatchXml", package.MspPackage.PatchXml); - writer.WriteAttributeString("DisplayInternalUI", package.MspPackage.DisplayInternalUI ? "yes" : "no"); - - // If there is still a chance that all of our patches will target a narrow set of - // product codes, add the patch list to the overall list. - if (null != targetCodes) - { - if (!package.MspPackage.TargetUnspecified) - { - IEnumerable patchTargetCodes = targetCodesByPatch[package.MspPackage.ChainPackageId]; - - targetCodes.AddRange(patchTargetCodes); - } - else // we have a patch that targets the world, so throw the whole list away. - { - targetCodes = null; - } - } - } - else if (WixBundlePackageType.Msu == package.Package.Type) - { - writer.WriteAttributeString("DetectCondition", package.MsuPackage.DetectCondition); - writer.WriteAttributeString("KB", package.MsuPackage.MsuKB); - } - - IEnumerable packageMsiFeatures = msiFeaturesByPackage[package.Package.WixChainItemId]; - - foreach (WixBundleMsiFeatureRow feature in packageMsiFeatures) - { - writer.WriteStartElement("MsiFeature"); - writer.WriteAttributeString("Id", feature.Name); - writer.WriteEndElement(); - } - - IEnumerable packageMsiProperties = msiPropertiesByPackage[package.Package.WixChainItemId]; - - foreach (WixBundleMsiPropertyRow msiProperty in packageMsiProperties) - { - writer.WriteStartElement("MsiProperty"); - writer.WriteAttributeString("Id", msiProperty.Name); - writer.WriteAttributeString("Value", msiProperty.Value); - if (!String.IsNullOrEmpty(msiProperty.Condition)) - { - writer.WriteAttributeString("Condition", msiProperty.Condition); - } - writer.WriteEndElement(); - } - - IEnumerable packageSlipstreamMsps = slipstreamMspsByPackage[package.Package.WixChainItemId]; - - foreach (WixBundleSlipstreamMspRow slipstreamMsp in packageSlipstreamMsps) - { - writer.WriteStartElement("SlipstreamMsp"); - writer.WriteAttributeString("Id", slipstreamMsp.MspPackageId); - writer.WriteEndElement(); - } - - IEnumerable packageExitCodes = exitCodesByPackage[package.Package.WixChainItemId]; - - foreach (WixBundlePackageExitCodeRow exitCode in packageExitCodes) - { - writer.WriteStartElement("ExitCode"); - - if (exitCode.Code.HasValue) - { - writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture)); - } - else - { - writer.WriteAttributeString("Code", "*"); - } - - writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture)); - writer.WriteEndElement(); - } - - IEnumerable packageCommandLines = commandLinesByPackage[package.Package.WixChainItemId]; - - foreach (WixBundlePackageCommandLineRow commandLine in packageCommandLines) - { - writer.WriteStartElement("CommandLine"); - writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); - writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument); - writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument); - writer.WriteAttributeString("Condition", commandLine.Condition); - writer.WriteEndElement(); - } - - // Output the dependency information. - foreach (ProvidesDependency dependency in package.Provides) - { - // TODO: Add to wixpdb as an imported table, or link package wixpdbs to bundle wixpdbs. - dependency.WriteXml(writer); - } - - IEnumerable packageRelatedPackages = relatedPackagesByPackage[package.Package.WixChainItemId]; - - foreach (WixBundleRelatedPackageRow related in packageRelatedPackages) - { - writer.WriteStartElement("RelatedPackage"); - writer.WriteAttributeString("Id", related.Id); - if (!String.IsNullOrEmpty(related.MinVersion)) - { - writer.WriteAttributeString("MinVersion", related.MinVersion); - writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no"); - } - if (!String.IsNullOrEmpty(related.MaxVersion)) - { - writer.WriteAttributeString("MaxVersion", related.MaxVersion); - writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no"); - } - writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); - - string[] relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (0 < relatedLanguages.Length) - { - writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no"); - foreach (string language in relatedLanguages) - { - writer.WriteStartElement("Language"); - writer.WriteAttributeString("Id", language); - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - } - - // Write any contained Payloads with the PackagePayload being first - writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", package.Package.PackagePayload); - writer.WriteEndElement(); - - IEnumerable packagePayloads = payloadsByPackage[package.Package.WixChainItemId]; - - foreach (WixBundlePayloadRow payload in packagePayloads) - { - if (payload.Id != package.Package.PackagePayload) - { - writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", payload.Id); - writer.WriteEndElement(); - } - } - - writer.WriteEndElement(); // - } - writer.WriteEndElement(); // - - if (null != targetCodes) - { - foreach (WixBundlePatchTargetCodeRow targetCode in targetCodes) - { - writer.WriteStartElement("PatchTargetCode"); - writer.WriteAttributeString("TargetCode", targetCode.TargetCode); - writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); - writer.WriteEndElement(); - } - } - - // Write the ApprovedExeForElevation elements. - IEnumerable approvedExesForElevation = this.Output.Tables["WixApprovedExeForElevation"].RowsAs(); - - foreach (WixApprovedExeForElevationRow approvedExeForElevation in approvedExesForElevation) - { - writer.WriteStartElement("ApprovedExeForElevation"); - writer.WriteAttributeString("Id", approvedExeForElevation.Id); - writer.WriteAttributeString("Key", approvedExeForElevation.Key); - - if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) - { - writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName); - } - - if (approvedExeForElevation.Win64) - { - writer.WriteAttributeString("Win64", "yes"); - } - - writer.WriteEndElement(); - } - - writer.WriteEndDocument(); // - } - } - - private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerRow container) - { - writer.WriteAttributeString("Id", container.Id); - writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", container.Hash); - - if (ContainerType.Detached == container.Type) - { - string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id, container.Name); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - writer.WriteAttributeString("DownloadUrl", resolvedUrl); - } - else if (!String.IsNullOrEmpty(container.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); - } - - writer.WriteAttributeString("FilePath", container.Name); - } - else if (ContainerType.Attached == container.Type) - { - if (!String.IsNullOrEmpty(container.DownloadUrl)) - { - Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id)); - } - - writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. - writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Attached", "yes"); - writer.WriteAttributeString("Primary", "yes"); - } - } - - private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadRow payload, bool embeddedOnly, Dictionary allPayloads) - { - Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging); - - writer.WriteAttributeString("Id", payload.Id); - writer.WriteAttributeString("FilePath", payload.Name); - writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", payload.Hash); - - if (payload.LayoutOnly) - { - writer.WriteAttributeString("LayoutOnly", "yes"); - } - - if (!String.IsNullOrEmpty(payload.PublicKey)) - { - writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.PublicKey); - } - - if (!String.IsNullOrEmpty(payload.Thumbprint)) - { - writer.WriteAttributeString("CertificateRootThumbprint", payload.Thumbprint); - } - - switch (payload.Packaging) - { - case PackagingType.Embedded: // this means it's in a container. - if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id)); - } - - writer.WriteAttributeString("Packaging", "embedded"); - writer.WriteAttributeString("SourcePath", payload.EmbeddedId); - - if (Compiler.BurnUXContainerId != payload.Container) - { - writer.WriteAttributeString("Container", payload.Container); - } - break; - - case PackagingType.External: - string packageId = payload.ParentPackagePayload; - string parentUrl = payload.ParentPackagePayload == null ? null : allPayloads[payload.ParentPackagePayload].DownloadUrl; - string resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id, payload.Name); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - writer.WriteAttributeString("DownloadUrl", resolvedUrl); - } - else if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); - } - - writer.WriteAttributeString("Packaging", "external"); - writer.WriteAttributeString("SourcePath", payload.Name); - break; - } - - if (!String.IsNullOrEmpty(payload.Catalog)) - { - writer.WriteAttributeString("Catalog", payload.Catalog); - } - } - - private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) - { - string resolved = null; - foreach (IBinderFileManager fileManager in this.FileManagers) - { - resolved = fileManager.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); - if (!String.IsNullOrEmpty(resolved)) - { - break; - } - } - - return resolved; - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs deleted file mode 100644 index 1bf987e3..00000000 --- a/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs +++ /dev/null @@ -1,68 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using WixToolset.Cab; - using WixToolset.Data; - using WixToolset.Data.Rows; - - /// - /// Creates cabinet files. - /// - internal class CreateContainerCommand : ICommand - { - public CompressionLevel DefaultCompressionLevel { private get; set; } - - public IEnumerable Payloads { private get; set; } - - public string ManifestFile { private get; set; } - - public string OutputPath { private get; set; } - - public string Hash { get; private set; } - - public long Size { get; private set; } - - public void Execute() - { - int payloadCount = this.Payloads.Count(); // The number of embedded payloads - - if (!String.IsNullOrEmpty(this.ManifestFile)) - { - ++payloadCount; - } - - using (WixCreateCab cab = new WixCreateCab(Path.GetFileName(this.OutputPath), Path.GetDirectoryName(this.OutputPath), payloadCount, 0, 0, this.DefaultCompressionLevel)) - { - // If a manifest was provided always add it as "payload 0" to the container. - if (!String.IsNullOrEmpty(this.ManifestFile)) - { - cab.AddFile(this.ManifestFile, "0"); - } - - foreach (WixBundlePayloadRow payload in this.Payloads) - { - Debug.Assert(PackagingType.Embedded == payload.Packaging); - - Messaging.Instance.OnMessage(WixVerboses.LoadingPayload(payload.FullFileName)); - - cab.AddFile(payload.FullFileName, payload.EmbeddedId); - } - - cab.Complete(); - } - - // Now that the container is created, set the outputs of the command. - FileInfo fileInfo = new FileInfo(this.OutputPath); - - this.Hash = Common.GetFileHash(fileInfo.FullName); - - this.Size = fileInfo.Length; - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs deleted file mode 100644 index dc19e380..00000000 --- a/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs +++ /dev/null @@ -1,62 +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.Bind.Bundles -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class GetPackageFacadesCommand : ICommand - { - public Table PackageTable { private get; set; } - - public Table ExePackageTable { private get; set; } - - public Table MsiPackageTable { private get; set; } - - public Table MspPackageTable { private get; set; } - - public Table MsuPackageTable { private get; set; } - - public IDictionary PackageFacades { get; private set; } - - public void Execute() - { - RowDictionary exePackages = new RowDictionary(this.ExePackageTable); - RowDictionary msiPackages = new RowDictionary(this.MsiPackageTable); - RowDictionary mspPackages = new RowDictionary(this.MspPackageTable); - RowDictionary msuPackages = new RowDictionary(this.MsuPackageTable); - - Dictionary facades = new Dictionary(this.PackageTable.Rows.Count); - - foreach (WixBundlePackageRow package in this.PackageTable.Rows) - { - string id = package.WixChainItemId; - PackageFacade facade = null; - - switch (package.Type) - { - case WixBundlePackageType.Exe: - facade = new PackageFacade(package, exePackages.Get(id)); - break; - - case WixBundlePackageType.Msi: - facade = new PackageFacade(package, msiPackages.Get(id)); - break; - - case WixBundlePackageType.Msp: - facade = new PackageFacade(package, mspPackages.Get(id)); - break; - - case WixBundlePackageType.Msu: - facade = new PackageFacade(package, msuPackages.Get(id)); - break; - } - - facades.Add(id, facade); - } - - this.PackageFacades = facades; - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs deleted file mode 100644 index ac3a301d..00000000 --- a/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs +++ /dev/null @@ -1,145 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class OrderPackagesAndRollbackBoundariesCommand : ICommand - { - public Table WixGroupTable { private get; set; } - - public RowDictionary Boundaries { private get; set; } - - public IDictionary PackageFacades { private get; set; } - - public IEnumerable OrderedPackageFacades { get; private set; } - - public IEnumerable UsedRollbackBoundaries { get; private set; } - - public void Execute() - { - List orderedFacades = new List(); - List usedBoundaries = new List(); - - // Process the chain of packages to add them in the correct order - // and assign the forward rollback boundaries as appropriate. Remember - // rollback boundaries are authored as elements in the chain which - // we re-interpret here to add them as attributes on the next available - // package in the chain. Essentially we mark some packages as being - // the start of a rollback boundary when installing and repairing. - // We handle uninstall (aka: backwards) rollback boundaries after - // we get these install/repair (aka: forward) rollback boundaries - // defined. - WixBundleRollbackBoundaryRow previousRollbackBoundary = null; - WixBundleRollbackBoundaryRow lastRollbackBoundary = null; - bool boundaryHadX86Package = false; - - foreach (WixGroupRow row in this.WixGroupTable.Rows) - { - if (ComplexReferenceChildType.Package == row.ChildType && ComplexReferenceParentType.PackageGroup == row.ParentType && "WixChain" == row.ParentId) - { - PackageFacade facade = null; - if (PackageFacades.TryGetValue(row.ChildId, out facade)) - { - if (null != previousRollbackBoundary) - { - usedBoundaries.Add(previousRollbackBoundary); - facade.Package.RollbackBoundary = previousRollbackBoundary.ChainPackageId; - previousRollbackBoundary = null; - - boundaryHadX86Package = (facade.Package.x64 == YesNoType.Yes); - } - - // Error if MSI transaction has x86 package preceding x64 packages - if ((lastRollbackBoundary != null) && (lastRollbackBoundary.Transaction == YesNoType.Yes) - && boundaryHadX86Package - && (facade.Package.x64 == YesNoType.Yes)) - { - Messaging.Instance.OnMessage(WixErrors.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); - } - boundaryHadX86Package = boundaryHadX86Package || (facade.Package.x64 == YesNoType.No); - - orderedFacades.Add(facade); - } - else // must be a rollback boundary. - { - // Discard the next rollback boundary if we have a previously defined boundary. - WixBundleRollbackBoundaryRow nextRollbackBoundary = Boundaries.Get(row.ChildId); - if (null != previousRollbackBoundary) - { - Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.ChainPackageId)); - } - else - { - previousRollbackBoundary = nextRollbackBoundary; - lastRollbackBoundary = nextRollbackBoundary; - } - } - } - } - - if (null != previousRollbackBoundary) - { - Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.ChainPackageId)); - } - - // With the forward rollback boundaries assigned, we can now go - // through the packages with rollback boundaries and assign backward - // rollback boundaries. Backward rollback boundaries are used when - // the chain is going "backwards" which (AFAIK) only happens during - // uninstall. - // - // Consider the scenario with three packages: A, B and C. Packages A - // and C are marked as rollback boundary packages and package B is - // not. The naive implementation would execute the chain like this - // (numbers indicate where rollback boundaries would end up): - // install: 1 A B 2 C - // uninstall: 2 C B 1 A - // - // The uninstall chain is wrong, A and B should be grouped together - // not C and B. The fix is to label packages with a "backwards" - // rollback boundary used during uninstall. The backwards rollback - // boundaries are assigned to the package *before* the next rollback - // boundary. Using our example from above again, I'll mark the - // backwards rollback boundaries prime (aka: with '). - // install: 1 A B 1' 2 C 2' - // uninstall: 2' C 2 1' B A 1 - // - // If the marked boundaries are ignored during install you get the - // same thing as above (good) and if the non-marked boundaries are - // ignored during uninstall then A and B are correctly grouped. - // Here's what it looks like without all the markers: - // install: 1 A B 2 C - // uninstall: 2 C 1 B A - // Woot! - string previousRollbackBoundaryId = null; - PackageFacade previousFacade = null; - - foreach (PackageFacade package in orderedFacades) - { - if (null != package.Package.RollbackBoundary) - { - if (null != previousFacade) - { - previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; - } - - previousRollbackBoundaryId = package.Package.RollbackBoundary; - } - - previousFacade = package; - } - - if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) - { - previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; - } - - this.OrderedPackageFacades = orderedFacades; - this.UsedRollbackBoundaries = usedBoundaries; - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs b/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs deleted file mode 100644 index f7e6410f..00000000 --- a/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs +++ /dev/null @@ -1,58 +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.Bind.Bundles -{ - using WixToolset.Data.Rows; - - internal class PackageFacade - { - private PackageFacade(WixBundlePackageRow package) - { - this.Package = package; - this.Provides = new ProvidesDependencyCollection(); - } - - public PackageFacade(WixBundlePackageRow package, WixBundleExePackageRow exePackage) - : this(package) - { - this.ExePackage = exePackage; - } - - public PackageFacade(WixBundlePackageRow package, WixBundleMsiPackageRow msiPackage) - : this(package) - { - this.MsiPackage = msiPackage; - } - - public PackageFacade(WixBundlePackageRow package, WixBundleMspPackageRow mspPackage) - : this(package) - { - this.MspPackage = mspPackage; - } - - public PackageFacade(WixBundlePackageRow package, WixBundleMsuPackageRow msuPackage) - : this(package) - { - this.MsuPackage = msuPackage; - } - - public WixBundlePackageRow Package { get; private set; } - - public WixBundleExePackageRow ExePackage { get; private set; } - - public WixBundleMsiPackageRow MsiPackage { get; private set; } - - public WixBundleMspPackageRow MspPackage { get; private set; } - - public WixBundleMsuPackageRow MsuPackage { get; private set; } - - /// - /// The provides dependencies authored and imported for this package. - /// - /// - /// TODO: Eventually this collection should turn into Rows so they are tracked in the PDB but - /// the relationship with the extension makes it much trickier to pull off. - /// - public ProvidesDependencyCollection Provides { get; private set; } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs deleted file mode 100644 index a1e7c271..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs +++ /dev/null @@ -1,33 +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.Bind.Bundles -{ - using System; - using WixToolset.Data; - using WixToolset.Data.Rows; - - /// - /// Initializes package state from the Exe contents. - /// - internal class ProcessExePackageCommand : ICommand - { - public RowDictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - /// - /// Processes the Exe packages to add properties and payloads from the Exe packages. - /// - public void Execute() - { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); - - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) - { - this.Facade.Package.CacheId = packagePayload.Hash; - } - - this.Facade.Package.Version = packagePayload.Version; - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs deleted file mode 100644 index f73776c0..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs +++ /dev/null @@ -1,560 +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.Bind.Bundles -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - using WixToolset.Msi; - using WixToolset.Core.Native; - using Dtf = WixToolset.Dtf.WindowsInstaller; - - /// - /// Initializes package state from the MSI contents. - /// - internal class ProcessMsiPackageCommand : ICommand - { - private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"; - - public RowDictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - public IBinderFileManager FileManager { private get; set; } - - public Table MsiFeatureTable { private get; set; } - - public Table MsiPropertyTable { private get; set; } - - public Table PayloadTable { private get; set; } - - public Table RelatedPackageTable { private get; set; } - - /// - /// Processes the MSI packages to add properties and payloads from the MSI packages. - /// - public void Execute() - { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); - - string sourcePath = packagePayload.FullFileName; - bool longNamesInImage = false; - bool compressed = false; - bool x64 = false; - try - { - // Read data out of the msi database... - using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) - { - // 1 is the Word Count summary information stream bit that means - // the MSI uses short file names when set. We care about long file - // names so check when the bit is not set. - longNamesInImage = 0 == (sumInfo.WordCount & 1); - - // 2 is the Word Count summary information stream bit that means - // files are compressed in the MSI by default when the bit is set. - compressed = 2 == (sumInfo.WordCount & 2); - - x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64")); - - // 8 is the Word Count summary information stream bit that means - // "Elevated privileges are not required to install this package." - // in MSI 4.5 and below, if this bit is 0, elevation is required. - this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No; - this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No; - } - - using (Dtf.Database db = new Dtf.Database(sourcePath)) - { - this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode"); - this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode"); - this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer"); - this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture); - this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion"); - - if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion)) - { - // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? - string version = null; - string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.'); - int count = versionParts.Length; - if (0 < count) - { - version = versionParts[0]; - for (int i = 1; i < 4 && i < count; ++i) - { - version = String.Concat(version, ".", versionParts[i]); - } - } - - if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version)) - { - Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version)); - this.Facade.MsiPackage.ProductVersion = version; - } - else - { - Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath)); - } - } - - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) - { - this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion); - } - - if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) - { - this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName"); - } - - if (String.IsNullOrEmpty(this.Facade.Package.Description)) - { - this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); - } - - ISet payloadNames = this.GetPayloadTargetNames(); - - ISet msiPropertyNames = this.GetMsiPropertyNames(); - - this.SetPerMachineAppropriately(db, sourcePath); - - // Ensure the MSI package is appropriately marked visible or not. - this.SetPackageVisibility(db, msiPropertyNames); - - // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. - if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL")) - { - this.AddMsiProperty("MSIFASTINSTALL", "7"); - } - - this.CreateRelatedPackages(db); - - // If feature selection is enabled, represent the Feature table in the manifest. - if (this.Facade.MsiPackage.EnableFeatureSelection) - { - this.CreateMsiFeatures(db); - } - - // Add all external cabinets as package payloads. - this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames); - - // Add all external files as package payloads and calculate the total install size as the rollup of - // File table's sizes. - this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); - - // Add all dependency providers from the MSI. - this.ImportDependencyProviders(db); - } - } - catch (Dtf.InstallerException e) - { - Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message)); - } - } - - private ISet GetPayloadTargetNames() - { - IEnumerable payloadNames = this.PayloadTable.RowsAs() - .Where(r => r.Package == this.Facade.Package.WixChainItemId) - .Select(r => r.Name); - - return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); - } - - private ISet GetMsiPropertyNames() - { - IEnumerable properties = this.MsiPropertyTable.RowsAs() - .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId) - .Select(r => r.Name); - - return new HashSet(properties, StringComparer.Ordinal); - } - - private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath) - { - if (this.Facade.MsiPackage.ForcePerMachine) - { - if (YesNoDefaultType.No == this.Facade.Package.PerMachine) - { - Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath)); - this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. - } - - // Force ALLUSERS=1 via the MSI command-line. - this.AddMsiProperty("ALLUSERS", "1"); - } - else - { - string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS"); - - if (String.IsNullOrEmpty(allusers)) - { - // Not forced per-machine and no ALLUSERS property, flip back to per-user. - if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) - { - Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath)); - this.Facade.Package.PerMachine = YesNoDefaultType.No; - } - } - else if (allusers.Equals("1", StringComparison.Ordinal)) - { - if (YesNoDefaultType.No == this.Facade.Package.PerMachine) - { - Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath)); - } - } - else if (allusers.Equals("2", StringComparison.Ordinal)) - { - Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user")); - } - else - { - Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers)); - } - } - } - - private void SetPackageVisibility(Dtf.Database db, ISet msiPropertyNames) - { - bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT"); - - if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility. - { - // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. - if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) - { - this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1"); - } - } - } - - private void CreateRelatedPackages(Dtf.Database db) - { - // Represent the Upgrade table as related packages. - if (db.Tables.Contains("Upgrade")) - { - using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) - { - view.Execute(); - while (true) - { - using (Dtf.Record record = view.Fetch()) - { - if (null == record) - { - break; - } - - WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers); - related.ChainPackageId = this.Facade.Package.WixChainItemId; - related.Id = record.GetString(1); - related.MinVersion = record.GetString(2); - related.MaxVersion = record.GetString(3); - related.Languages = record.GetString(4); - - int attributes = record.GetInteger(5); - related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect; - related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; - related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; - related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0; - } - } - } - } - } - - private void CreateMsiFeatures(Dtf.Database db) - { - if (db.Tables.Contains("Feature")) - { - using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) - using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) - { - using (Dtf.Record featureRecord = new Dtf.Record(1)) - using (Dtf.Record componentRecord = new Dtf.Record(1)) - { - using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) - { - allFeaturesView.Execute(); - - while (true) - { - using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch()) - { - if (null == allFeaturesResultRecord) - { - break; - } - - string featureName = allFeaturesResultRecord.GetString(1); - - // Calculate the Feature size. - featureRecord.SetString(1, featureName); - featureView.Execute(featureRecord); - - // Loop over all the components for the feature to calculate the size of the feature. - long size = 0; - while (true) - { - using (Dtf.Record componentResultRecord = featureView.Fetch()) - { - if (null == componentResultRecord) - { - break; - } - string component = componentResultRecord.GetString(1); - componentRecord.SetString(1, component); - componentView.Execute(componentRecord); - - while (true) - { - using (Dtf.Record fileResultRecord = componentView.Fetch()) - { - if (null == fileResultRecord) - { - break; - } - - string fileSize = fileResultRecord.GetString(1); - size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); - } - } - } - } - - WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers); - feature.ChainPackageId = this.Facade.Package.WixChainItemId; - feature.Name = featureName; - feature.Parent = allFeaturesResultRecord.GetString(2); - feature.Title = allFeaturesResultRecord.GetString(3); - feature.Description = allFeaturesResultRecord.GetString(4); - feature.Display = allFeaturesResultRecord.GetInteger(5); - feature.Level = allFeaturesResultRecord.GetInteger(6); - feature.Directory = allFeaturesResultRecord.GetString(7); - feature.Attributes = allFeaturesResultRecord.GetInteger(8); - feature.Size = size; - } - } - } - } - } - } - } - - private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet payloadNames) - { - if (db.Tables.Contains("Media")) - { - foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) - { - if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) - { - // If we didn't find the Payload as an existing child of the package, we need to - // add it. We expect the file to exist on-disk in the same relative location as - // the MSI expects to find it... - string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); - - if (!payloadNames.Contains(cabinetName)) - { - string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet); - string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal); - - WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); - payload.Id = generatedId; - payload.Name = cabinetName; - payload.SourceFile = payloadSourceFile; - payload.Compressed = packagePayload.Compressed; - payload.UnresolvedSourceFile = cabinetName; - payload.Package = packagePayload.Package; - payload.Container = packagePayload.Container; - payload.ContentFile = true; - payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; - payload.Packaging = packagePayload.Packaging; - payload.ParentPackagePayload = packagePayload.Id; - } - } - } - } - } - - private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) - { - long size = 0; - - if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File")) - { - Hashtable directories = new Hashtable(); - - // Load up the directory hash table so we will be able to resolve source paths - // for files in the MSI database. - using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) - { - view.Execute(); - while (true) - { - using (Dtf.Record record = view.Fetch()) - { - if (null == record) - { - break; - } - - string sourceName = Installer.GetName(record.GetString(3), true, longNamesInImage); - directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName)); - } - } - } - - // Resolve the source paths to external files and add each file size to the total - // install size of the package. - using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) - { - view.Execute(); - while (true) - { - using (Dtf.Record record = view.Fetch()) - { - if (null == record) - { - break; - } - - // Skip adding the loose files as payloads if it was suppressed. - if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration) - { - // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not - // explicitly marked compressed then this is an external file. - if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) || - (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed))) - { - string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); - string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); - - if (!payloadNames.Contains(name)) - { - string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2)); - string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal); - - WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); - payload.Id = generatedId; - payload.Name = name; - payload.SourceFile = payloadSourceFile; - payload.Compressed = packagePayload.Compressed; - payload.UnresolvedSourceFile = name; - payload.Package = packagePayload.Package; - payload.Container = packagePayload.Container; - payload.ContentFile = true; - payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; - payload.Packaging = packagePayload.Packaging; - payload.ParentPackagePayload = packagePayload.Id; - } - } - } - - size += record.GetInteger(5); - } - } - } - } - - return size; - } - - private void AddMsiProperty(string name, string value) - { - WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers); - row.ChainPackageId = this.Facade.Package.WixChainItemId; - row.Name = name; - row.Value = value; - } - - private void ImportDependencyProviders(Dtf.Database db) - { - if (db.Tables.Contains("WixDependencyProvider")) - { - string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; - - using (Dtf.View view = db.OpenView(query)) - { - view.Execute(); - while (true) - { - using (Dtf.Record record = view.Fetch()) - { - if (null == record) - { - break; - } - - // Import the provider key and attributes. - string providerKey = record.GetString(1); - string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion; - string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName; - int attributes = record.GetInteger(4); - - ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes); - dependency.Imported = true; - - this.Facade.Provides.Add(dependency); - } - } - } - } - } - - /// - /// Queries a Windows Installer database for a Property value. - /// - /// Database to query. - /// Property to examine. - /// String value for result or null if query doesn't match a single result. - private static string GetProperty(Dtf.Database db, string property) - { - try - { - return db.ExecuteScalar(PropertyQuery(property)).ToString(); - } - catch (Dtf.InstallerException) - { - } - - return null; - } - - /// - /// Queries a Windows Installer database to determine if one or more rows exist in the Property table. - /// - /// Database to query. - /// Property to examine. - /// True if query matches at least one result. - private static bool HasProperty(Dtf.Database db, string property) - { - try - { - return 0 < db.ExecuteQuery(PropertyQuery(property)).Count; - } - catch (Dtf.InstallerException) - { - } - - return false; - } - - private static string PropertyQuery(string property) - { - // quick sanity check that we'll be creating a valid query... - // TODO: Are there any other special characters we should be looking for? - Debug.Assert(!property.Contains("'")); - - return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property); - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs deleted file mode 100644 index 24063221..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs +++ /dev/null @@ -1,189 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Rows; - using Dtf = WixToolset.Dtf.WindowsInstaller; - - /// - /// Initializes package state from the Msp contents. - /// - internal class ProcessMspPackageCommand : ICommand - { - private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'"; - private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false); - - public RowDictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - public Table WixBundlePatchTargetCodeTable { private get; set; } - - /// - /// Processes the Msp packages to add properties and payloads from the Msp packages. - /// - public void Execute() - { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); - - string sourcePath = packagePayload.FullFileName; - - try - { - // Read data out of the msp database... - using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) - { - this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); - } - - using (Dtf.Database db = new Dtf.Database(sourcePath)) - { - if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) - { - this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); - } - - if (String.IsNullOrEmpty(this.Facade.Package.Description)) - { - this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); - } - - this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); - } - - this.ProcessPatchXml(packagePayload, sourcePath); - } - catch (Dtf.InstallerException e) - { - Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); - return; - } - - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) - { - this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode; - } - } - - private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath) - { - HashSet uniqueTargetCodes = new HashSet(); - - string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); - - XmlDocument doc = new XmlDocument(); - doc.LoadXml(patchXml); - - XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); - nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); - - // Determine target ProductCodes and/or UpgradeCodes. - foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) - { - // If this patch targets a product code, this is the best case. - XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); - WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; - - if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) - { - attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; - } - else // maybe targets an upgrade code? - { - targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); - if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) - { - attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; - } - else // this patch targets an unknown number of products - { - this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; - } - } - - string targetCode = targetCodeElement.InnerText; - - if (uniqueTargetCodes.Add(targetCode)) - { - WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers); - row.MspPackageId = packagePayload.Id; - row.TargetCode = targetCode; - row.Attributes = attributes; - } - } - - // Suppress patch sequence data for improved performance. - XmlNode root = doc.DocumentElement; - foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) - { - root.RemoveChild(node); - } - - // Save the XML as compact as possible. - using (StringWriter writer = new StringWriter()) - { - XmlWriterSettings settings = new XmlWriterSettings() - { - Encoding = ProcessMspPackageCommand.XmlOutputEncoding, - Indent = false, - NewLineChars = string.Empty, - NewLineHandling = NewLineHandling.Replace, - }; - - using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) - { - doc.WriteTo(xmlWriter); - } - - this.Facade.MspPackage.PatchXml = writer.ToString(); - } - } - - /// - /// Queries a Windows Installer patch database for a Property value from the MsiPatchMetadata table. - /// - /// Database to query. - /// Property to examine. - /// String value for result or null if query doesn't match a single result. - private static string GetPatchMetadataProperty(Dtf.Database db, string property) - { - try - { - return db.ExecuteScalar(PatchMetadataPropertyQuery(property)).ToString(); - } - catch (Dtf.InstallerException) - { - } - - return null; - } - - private static string PatchMetadataPropertyQuery(string property) - { - // quick sanity check that we'll be creating a valid query... - // TODO: Are there any other special characters we should be looking for? - Debug.Assert(!property.Contains("'")); - - return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property); - } - - private static bool TargetsCode(XmlNode node) - { - if (null != node) - { - XmlAttribute attr = node.Attributes["Validate"]; - return null != attr && "true".Equals(attr.Value); - } - - return false; - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs deleted file mode 100644 index ba59f5f5..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs +++ /dev/null @@ -1,30 +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.Bind.Bundles -{ - using System; - using WixToolset.Data; - using WixToolset.Data.Rows; - - /// - /// Processes the Msu packages to add properties and payloads from the Msu packages. - /// - internal class ProcessMsuPackageCommand : ICommand - { - public RowDictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - public void Execute() - { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); - - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) - { - this.Facade.Package.CacheId = packagePayload.Hash; - } - - this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs deleted file mode 100644 index a83a7a4a..00000000 --- a/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs +++ /dev/null @@ -1,159 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Security.Cryptography; - using System.Security.Cryptography.X509Certificates; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class ProcessPayloadsCommand : ICommand - { - private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); - - public IEnumerable Payloads { private get; set; } - - public PackagingType DefaultPackaging { private get; set; } - - public string LayoutDirectory { private get; set; } - - public IEnumerable FileTransfers { get; private set; } - - public void Execute() - { - List fileTransfers = new List(); - - foreach (WixBundlePayloadRow payload in this.Payloads) - { - string normalizedPath = payload.Name.Replace('\\', '/'); - if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) - { - Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); - } - - // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden - // in the .wixlib). - ObjectField field = (ObjectField)payload.Fields[2]; - payload.ContentFile = !field.EmbeddedFileIndex.HasValue; - - this.UpdatePayloadPackagingType(payload); - - if (String.IsNullOrEmpty(payload.SourceFile)) - { - // Remote payloads obviously cannot be embedded. - Debug.Assert(PackagingType.Embedded != payload.Packaging); - } - else // not a remote payload so we have a lot more to update. - { - this.UpdatePayloadFileInformation(payload); - - this.UpdatePayloadVersionInformation(payload); - - // External payloads need to be transfered. - if (PackagingType.External == payload.Packaging) - { - FileTransfer transfer; - if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer)) - { - fileTransfers.Add(transfer); - } - } - } - } - - this.FileTransfers = fileTransfers; - } - - private void UpdatePayloadPackagingType(WixBundlePayloadRow payload) - { - if (PackagingType.Unknown == payload.Packaging) - { - if (YesNoDefaultType.Yes == payload.Compressed) - { - payload.Packaging = PackagingType.Embedded; - } - else if (YesNoDefaultType.No == payload.Compressed) - { - payload.Packaging = PackagingType.External; - } - else - { - payload.Packaging = this.DefaultPackaging; - } - } - - // Embedded payloads that are not assigned a container already are placed in the default attached - // container. - if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container)) - { - payload.Container = Compiler.BurnDefaultAttachedContainerId; - } - } - - private void UpdatePayloadFileInformation(WixBundlePayloadRow payload) - { - FileInfo fileInfo = new FileInfo(payload.SourceFile); - - if (null != fileInfo) - { - payload.FileSize = (int)fileInfo.Length; - - payload.Hash = Common.GetFileHash(fileInfo.FullName); - - // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation. - if (payload.EnableSignatureValidation) - { - X509Certificate2 certificate = null; - try - { - certificate = new X509Certificate2(fileInfo.FullName); - } - catch (CryptographicException) // we don't care about non-signed files. - { - } - - // If there is a certificate, remember its hashed public key identifier and thumbprint. - if (null != certificate) - { - byte[] publicKeyIdentifierHash = new byte[128]; - uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length; - - WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); - StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); - for (int i = 0; i < publicKeyIdentifierHashSize; ++i) - { - sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]); - } - - payload.PublicKey = sb.ToString(); - payload.Thumbprint = certificate.Thumbprint; - } - } - } - } - - private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload) - { - FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile); - - if (null != versionInfo) - { - // Use the fixed version info block for the file since the resource text may not be a dotted quad. - Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); - - if (ProcessPayloadsCommand.EmptyVersion != version) - { - payload.Version = version.ToString(); - } - - payload.Description = versionInfo.FileDescription; - payload.DisplayName = versionInfo.ProductName; - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs deleted file mode 100644 index 9c614c26..00000000 --- a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs +++ /dev/null @@ -1,148 +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.Bind.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class VerifyPayloadsWithCatalogCommand : ICommand - { - public IEnumerable Catalogs { private get; set; } - - public IEnumerable Payloads { private get; set; } - - public void Execute() - { - List catalogIdsWithPaths = this.Catalogs - .Join(this.Payloads, - catalog => catalog.Payload, - payload => payload.Id, - (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id, FullPath = Path.GetFullPath(payload.SourceFile) }) - .ToList(); - - foreach (WixBundlePayloadRow payloadInfo in this.Payloads) - { - // Payloads that are not embedded should be verfied. - if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) - { - bool validated = false; - - foreach (CatalogIdWithPath catalog in catalogIdsWithPaths) - { - if (!validated) - { - // Get the file hash - uint cryptHashSize = 20; - byte[] cryptHashBytes = new byte[cryptHashSize]; - int error; - IntPtr fileHandle = IntPtr.Zero; - using (FileStream payloadStream = File.OpenRead(payloadInfo.FullFileName)) - { - // Get the file handle - fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); - - // 20 bytes is usually the hash size. Future hashes may be bigger - if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) - { - error = Marshal.GetLastWin32Error(); - - if (VerifyInterop.ErrorInsufficientBuffer == error) - { - error = 0; - cryptHashBytes = new byte[cryptHashSize]; - if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) - { - error = Marshal.GetLastWin32Error(); - } - } - - if (0 != error) - { - Messaging.Instance.OnMessage(WixErrors.CatalogFileHashFailed(payloadInfo.FullFileName, error)); - } - } - } - - VerifyInterop.WinTrustCatalogInfo catalogData = new VerifyInterop.WinTrustCatalogInfo(); - VerifyInterop.WinTrustData trustData = new VerifyInterop.WinTrustData(); - try - { - // Create WINTRUST_CATALOG_INFO structure - catalogData.cbStruct = (uint)Marshal.SizeOf(catalogData); - catalogData.cbCalculatedFileHash = cryptHashSize; - catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize); - Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize); - - StringBuilder hashString = new StringBuilder(); - foreach (byte hashByte in cryptHashBytes) - { - hashString.Append(hashByte.ToString("X2")); - } - catalogData.pcwszMemberTag = hashString.ToString(); - - // The file names need to be lower case for older OSes - catalogData.pcwszMemberFilePath = payloadInfo.FullFileName.ToLowerInvariant(); - catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant(); - - // Create WINTRUST_DATA structure - trustData.cbStruct = (uint)Marshal.SizeOf(trustData); - trustData.dwUIChoice = VerifyInterop.WTD_UI_NONE; - trustData.fdwRevocationChecks = VerifyInterop.WTD_REVOKE_NONE; - trustData.dwUnionChoice = VerifyInterop.WTD_CHOICE_CATALOG; - trustData.dwStateAction = VerifyInterop.WTD_STATEACTION_VERIFY; - trustData.dwProvFlags = VerifyInterop.WTD_REVOCATION_CHECK_NONE; - - // Create the structure pointers for unmanaged - trustData.pCatalog = Marshal.AllocCoTaskMem(Marshal.SizeOf(catalogData)); - Marshal.StructureToPtr(catalogData, trustData.pCatalog, false); - - // Call WinTrustVerify to validate the file with the catalog - IntPtr noWindow = new IntPtr(-1); - Guid verifyGuid = new Guid(VerifyInterop.GenericVerify2); - long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData); - if (0 == verifyResult) - { - payloadInfo.Catalog = catalog.Id; - validated = true; - break; - } - } - finally - { - // Free the structure memory - if (IntPtr.Zero != trustData.pCatalog) - { - Marshal.FreeCoTaskMem(trustData.pCatalog); - } - - if (IntPtr.Zero != catalogData.pbCalculatedFileHash) - { - Marshal.FreeCoTaskMem(catalogData.pbCalculatedFileHash); - } - } - } - } - - // Error message if the file was not validated by one of the catalogs - if (!validated) - { - Messaging.Instance.OnMessage(WixErrors.CatalogVerificationFailed(payloadInfo.FullFileName)); - } - } - } - } - - private class CatalogIdWithPath - { - public string Id { get; set; } - - public string FullPath { get; set; } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs deleted file mode 100644 index 5e2650e9..00000000 --- a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs +++ /dev/null @@ -1,314 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.Rows; - - /// - /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. - /// - public class AssignMediaCommand : ICommand - { - public AssignMediaCommand() - { - this.CabinetNameTemplate = "Cab{0}.cab"; - } - - public Output Output { private get; set; } - - public bool FilesCompressed { private get; set; } - - public string CabinetNameTemplate { private get; set; } - - public IEnumerable FileFacades { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - /// - /// Gets cabinets with their file rows. - /// - public Dictionary> FileFacadesByCabinetMedia { get; private set; } - - /// - /// Get media rows. - /// - public RowDictionary MediaRows { get; private set; } - - /// - /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. - /// This contains all the files when Package element is marked with compression=no - /// - public IEnumerable UncompressedFileFacades { get; private set; } - - public void Execute() - { - Dictionary> filesByCabinetMedia = new Dictionary>(); - - RowDictionary mediaRows = new RowDictionary(); - - List uncompressedFiles = new List(); - - MediaRow mergeModuleMediaRow = null; - Table mediaTable = this.Output.Tables["Media"]; - Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; - - // If both tables are authored, it is an error. - if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1)) - { - throw new WixException(WixErrors.MediaTableCollision(null)); - } - - // When building merge module, all the files go to "#MergeModule.CABinet". - if (OutputType.Module == this.Output.Type) - { - Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]); - mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null); - mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet"; - - filesByCabinetMedia.Add(mergeModuleMediaRow, new List()); - } - - if (OutputType.Module == this.Output.Type || null == mediaTemplateTable) - { - this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); - } - else - { - this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); - } - - this.FileFacadesByCabinetMedia = new Dictionary>(); - - foreach (var mediaRowWithFiles in filesByCabinetMedia) - { - this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value); - } - - this.MediaRows = mediaRows; - - this.UncompressedFileFacades = uncompressedFiles; - } - - /// - /// Assign files to cabinets based on MediaTemplate authoring. - /// - /// FileRowCollection - private void AutoAssignFiles(Table mediaTable, IEnumerable fileFacades, Dictionary> filesByCabinetMedia, RowDictionary mediaRows, List uncompressedFiles) - { - const int MaxCabIndex = 999; - - ulong currentPreCabSize = 0; - ulong maxPreCabSizeInBytes; - int maxPreCabSizeInMB = 0; - int currentCabIndex = 0; - - MediaRow currentMediaRow = null; - - Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; - - // Auto assign files to cabinets based on maximum uncompressed media size - mediaTable.Rows.Clear(); - WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; - - if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) - { - this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; - } - - string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); - - try - { - // Override authored mums value if environment variable is authored. - if (!String.IsNullOrEmpty(mumsString)) - { - maxPreCabSizeInMB = Int32.Parse(mumsString); - } - else - { - maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; - } - - maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; - } - catch (FormatException) - { - throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); - } - catch (OverflowException) - { - throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); - } - - foreach (FileFacade facade in this.FileFacades) - { - // When building a product, if the current file is not to be compressed or if - // the package set not to be compressed, don't cab it. - if (OutputType.Product == this.Output.Type && - (YesNoType.No == facade.File.Compressed || - (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) - { - uncompressedFiles.Add(facade); - continue; - } - - if (currentCabIndex == MaxCabIndex) - { - // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. - List cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.WixFile.DiskId = currentCabIndex; - cabinetFiles.Add(facade); - continue; - } - - // Update current cab size. - currentPreCabSize += (ulong)facade.File.FileSize; - - if (currentPreCabSize > maxPreCabSizeInBytes) - { - // Overflow due to current file - currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); - mediaRows.Add(currentMediaRow); - filesByCabinetMedia.Add(currentMediaRow, new List()); - - List cabinetFileRows = filesByCabinetMedia[currentMediaRow]; - facade.WixFile.DiskId = currentCabIndex; - cabinetFileRows.Add(facade); - // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize - currentPreCabSize = (ulong)facade.File.FileSize; - } - else - { - // File fits in the current cab. - if (currentMediaRow == null) - { - // Create new cab and MediaRow - currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); - mediaRows.Add(currentMediaRow); - filesByCabinetMedia.Add(currentMediaRow, new List()); - } - - // Associate current file with current cab. - List cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.WixFile.DiskId = currentCabIndex; - cabinetFiles.Add(facade); - } - } - - // If there are uncompressed files and no MediaRow, create a default one. - if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0) - { - MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null); - defaultMediaRow.DiskId = 1; - mediaRows.Add(defaultMediaRow); - } - } - - /// - /// Assign files to cabinets based on Media authoring. - /// - /// - /// - /// - private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable fileFacades, Dictionary> filesByCabinetMedia, RowDictionary mediaRows, List uncompressedFiles) - { - if (OutputType.Module != this.Output.Type) - { - if (null != mediaTable) - { - Dictionary cabinetMediaRows = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (MediaRow mediaRow in mediaTable.Rows) - { - // If the Media row has a cabinet, make sure it is unique across all Media rows. - if (!String.IsNullOrEmpty(mediaRow.Cabinet)) - { - MediaRow existingRow; - if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow)) - { - Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); - Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); - } - else - { - cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); - } - } - - mediaRows.Add(mediaRow); - } - } - - foreach (MediaRow mediaRow in mediaRows.Values) - { - if (null != mediaRow.Cabinet) - { - filesByCabinetMedia.Add(mediaRow, new List()); - } - } - } - - foreach (FileFacade facade in fileFacades) - { - if (OutputType.Module == this.Output.Type) - { - filesByCabinetMedia[mergeModuleMediaRow].Add(facade); - } - else - { - MediaRow mediaRow; - if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow)) - { - Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId)); - continue; - } - - // When building a product, if the current file is not to be compressed or if - // the package set not to be compressed, don't cab it. - if (OutputType.Product == this.Output.Type && - (YesNoType.No == facade.File.Compressed || - (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed))) - { - uncompressedFiles.Add(facade); - } - else // file is marked compressed. - { - List cabinetFiles; - if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles)) - { - cabinetFiles.Add(facade); - } - else - { - Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId)); - } - } - } - } - } - - /// - /// Adds a row to the media table with cab name template filled in. - /// - /// - /// - /// - private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex) - { - MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); - currentMediaRow.DiskId = cabIndex; - currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex); - - Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]); - WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); - row.DiskId = cabIndex; - row.CompressionLevel = mediaTemplateRow.CompressionLevel; - - return currentMediaRow; - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs deleted file mode 100644 index 95bd4cf0..00000000 --- a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs +++ /dev/null @@ -1,135 +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.Bind.Databases -{ - using System; - using System.Globalization; - using WixToolset.Data; - - /// - /// Binds the summary information table of a database. - /// - internal class BindSummaryInfoCommand : ICommand - { - /// - /// The output to bind. - /// - public Output Output { private get; set; } - - /// - /// Returns a flag indicating if files are compressed by default. - /// - public bool Compressed { get; private set; } - - /// - /// Returns a flag indicating if uncompressed files use long filenames. - /// - public bool LongNames { get; private set; } - - public int InstallerVersion { get; private set; } - - /// - /// Modularization guid, or null if the output is not a module. - /// - public string ModularizationGuid { get; private set; } - - public void Execute() - { - this.Compressed = false; - this.LongNames = false; - this.InstallerVersion = 0; - this.ModularizationGuid = null; - - Table summaryInformationTable = this.Output.Tables["_SummaryInformation"]; - - if (null != summaryInformationTable) - { - bool foundCreateDataTime = false; - bool foundLastSaveDataTime = false; - bool foundCreatingApplication = false; - string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); - - foreach (Row summaryInformationRow in summaryInformationTable.Rows) - { - switch (summaryInformationRow.FieldAsInteger(0)) - { - case 1: // PID_CODEPAGE - // make sure the code page is an int and not a web name or null - string codepage = summaryInformationRow.FieldAsString(1); - - if (null == codepage) - { - codepage = "0"; - } - else - { - summaryInformationRow[1] = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture); - } - break; - case 9: // PID_REVNUMBER - string packageCode = (string)summaryInformationRow[1]; - - if (OutputType.Module == this.Output.Type) - { - this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_'); - } - else if ("*" == packageCode) - { - // set the revision number (package/patch code) if it should be automatically generated - summaryInformationRow[1] = Common.GenerateGuid(); - } - break; - case 12: // PID_CREATE_DTM - foundCreateDataTime = true; - break; - case 13: // PID_LASTSAVE_DTM - foundLastSaveDataTime = true; - break; - case 14: - this.InstallerVersion = summaryInformationRow.FieldAsInteger(1); - break; - case 15: // PID_WORDCOUNT - if (OutputType.Patch == this.Output.Type) - { - this.LongNames = true; - this.Compressed = true; - } - else - { - this.LongNames = (0 == (summaryInformationRow.FieldAsInteger(1) & 1)); - this.Compressed = (2 == (summaryInformationRow.FieldAsInteger(1) & 2)); - } - break; - case 18: // PID_APPNAME - foundCreatingApplication = true; - break; - } - } - - // add a summary information row for the create time/date property if its not already set - if (!foundCreateDataTime) - { - Row createTimeDateRow = summaryInformationTable.CreateRow(null); - createTimeDateRow[0] = 12; - createTimeDateRow[1] = now; - } - - // add a summary information row for the last save time/date property if its not already set - if (!foundLastSaveDataTime) - { - Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null); - lastSaveTimeDateRow[0] = 13; - lastSaveTimeDateRow[1] = now; - } - - // add a summary information row for the creating application property if its not already set - if (!foundCreatingApplication) - { - Row creatingApplicationRow = summaryInformationTable.CreateRow(null); - creatingApplicationRow[0] = 18; - creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString()); - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs deleted file mode 100644 index 2de6ec25..00000000 --- a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs +++ /dev/null @@ -1,176 +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.Bind.Databases -{ - using System; - using System.Collections; - using System.IO; - using System.Linq; - using System.Threading; - using WixToolset.Cab; - using WixToolset.Data; - using WixToolset.Data.Rows; - - /// - /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple - /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished. - /// - internal sealed class CabinetBuilder - { - private Queue cabinetWorkItems; - private object lockObject; - private int threadCount; - - // Address of Binder's callback function for Cabinet Splitting - private IntPtr newCabNamesCallBackAddress; - - public int MaximumCabinetSizeForLargeFileSplitting { get; set; } - public int MaximumUncompressedMediaSize { get; set; } - - /// - /// Instantiate a new CabinetBuilder. - /// - /// number of threads to use - /// Address of Binder's callback function for Cabinet Splitting - public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress) - { - if (0 >= threadCount) - { - throw new ArgumentOutOfRangeException("threadCount"); - } - - this.cabinetWorkItems = new Queue(); - this.lockObject = new object(); - - this.threadCount = threadCount; - - // Set Address of Binder's callback function for Cabinet Splitting - this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; - } - - /// - /// Enqueues a CabinetWorkItem to the queue. - /// - /// cabinet work item - public void Enqueue(CabinetWorkItem cabinetWorkItem) - { - this.cabinetWorkItems.Enqueue(cabinetWorkItem); - } - - /// - /// Create the queued cabinets. - /// - /// error message number (zero if no error) - public void CreateQueuedCabinets() - { - // don't create more threads than the number of cabinets to build - if (this.cabinetWorkItems.Count < this.threadCount) - { - this.threadCount = this.cabinetWorkItems.Count; - } - - if (0 < this.threadCount) - { - Thread[] threads = new Thread[this.threadCount]; - - for (int i = 0; i < threads.Length; i++) - { - threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); - threads[i].Start(); - } - - // wait for all threads to finish - foreach (Thread thread in threads) - { - thread.Join(); - } - } - } - - /// - /// This function gets called by multiple threads to do actual work. - /// It takes one work item at a time and calls this.CreateCabinet(). - /// It does not return until cabinetWorkItems queue is empty - /// - private void ProcessWorkItems() - { - try - { - while (true) - { - CabinetWorkItem cabinetWorkItem; - - lock (this.cabinetWorkItems) - { - // check if there are any more cabinets to create - if (0 == this.cabinetWorkItems.Count) - { - break; - } - - cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue(); - } - - // create a cabinet - this.CreateCabinet(cabinetWorkItem); - } - } - catch (WixException we) - { - Messaging.Instance.OnMessage(we.Error); - } - catch (Exception e) - { - Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace)); - } - } - - /// - /// Creates a cabinet using the wixcab.dll interop layer. - /// - /// CabinetWorkItem containing information about the cabinet to create. - private void CreateCabinet(CabinetWorkItem cabinetWorkItem) - { - Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile)); - - int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting - ulong maxPreCompressedSizeInBytes = 0; - - if (MaximumCabinetSizeForLargeFileSplitting != 0) - { - // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize - // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file - if (1 == cabinetWorkItem.FileFacades.Count()) - { - // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs - // Get the Value for Max Uncompressed Media Size - maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024; - - foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row - { - if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes) - { - // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting - maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting; - } - } - } - } - - // create the cabinet file - string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile); - string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile); - - using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel)) - { - foreach (FileFacade facade in cabinetWorkItem.FileFacades) - { - cab.AddFile(facade); - } - - cab.Complete(newCabNamesCallBackAddress); - } - } - } -} - diff --git a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs deleted file mode 100644 index 20241bc9..00000000 --- a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs +++ /dev/null @@ -1,78 +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.Bind.Databases -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Rows; - - /// - /// A cabinet builder work item. - /// - internal sealed class CabinetWorkItem - { - private string cabinetFile; - private CompressionLevel compressionLevel; - //private BinderFileManager binderFileManager; - private int maxThreshold; - - /// - /// Instantiate a new CabinetWorkItem. - /// - /// The collection of files in this cabinet. - /// The cabinet file. - /// Maximum threshold for each cabinet. - /// The compression level of the cabinet. - /// The binder file manager. - public CabinetWorkItem(IEnumerable fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/) - { - this.cabinetFile = cabinetFile; - this.compressionLevel = compressionLevel; - this.FileFacades = fileFacades; - //this.binderFileManager = binderFileManager; - this.maxThreshold = maxThreshold; - } - - /// - /// Gets the cabinet file. - /// - /// The cabinet file. - public string CabinetFile - { - get { return this.cabinetFile; } - } - - /// - /// Gets the compression level of the cabinet. - /// - /// The compression level of the cabinet. - public CompressionLevel CompressionLevel - { - get { return this.compressionLevel; } - } - - /// - /// Gets the collection of files in this cabinet. - /// - /// The collection of files in this cabinet. - public IEnumerable FileFacades { get; private set; } - - /// - /// Gets the binder file manager. - /// - /// The binder file manager. - //public BinderFileManager BinderFileManager - //{ - // get { return this.binderFileManager; } - //} - - /// - /// Gets the max threshold. - /// - /// The maximum threshold for a folder in a cabinet. - public int MaxThreshold - { - get { return this.maxThreshold; } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs deleted file mode 100644 index 7cb18e0f..00000000 --- a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs +++ /dev/null @@ -1,91 +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.Bind.Databases -{ - using System; - using System.Collections; - using System.Globalization; - using WixToolset.MergeMod; - - /// - /// Callback object for configurable merge modules. - /// - internal sealed class ConfigurationCallback : IMsmConfigureModule - { - private const int SOk = 0x0; - private const int SFalse = 0x1; - private Hashtable configurationData; - - /// - /// Creates a ConfigurationCallback object. - /// - /// String to break up into name/value pairs. - public ConfigurationCallback(string configData) - { - if (String.IsNullOrEmpty(configData)) - { - throw new ArgumentNullException("configData"); - } - - string[] pairs = configData.Split(','); - this.configurationData = new Hashtable(pairs.Length); - for (int i = 0; i < pairs.Length; ++i) - { - string[] nameVal = pairs[i].Split('='); - string name = nameVal[0]; - string value = nameVal[1]; - - name = name.Replace("%2C", ","); - name = name.Replace("%3D", "="); - name = name.Replace("%25", "%"); - - value = value.Replace("%2C", ","); - value = value.Replace("%3D", "="); - value = value.Replace("%25", "%"); - - this.configurationData[name] = value; - } - } - - /// - /// Returns text data based on name. - /// - /// Name of value to return. - /// Out param to put configuration data into. - /// S_OK if value provided, S_FALSE if not. - public int ProvideTextData(string name, out string configData) - { - if (this.configurationData.Contains(name)) - { - configData = (string)this.configurationData[name]; - return SOk; - } - else - { - configData = null; - return SFalse; - } - } - - /// - /// Returns integer data based on name. - /// - /// Name of value to return. - /// Out param to put configuration data into. - /// S_OK if value provided, S_FALSE if not. - public int ProvideIntegerData(string name, out int configData) - { - if (this.configurationData.Contains(name)) - { - string val = (string)this.configurationData[name]; - configData = Convert.ToInt32(val, CultureInfo.InvariantCulture); - return SOk; - } - else - { - configData = 0; - return SFalse; - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs deleted file mode 100644 index af1ab3b0..00000000 --- a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs +++ /dev/null @@ -1,606 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - using WixToolset.Core.Native; - - internal class CopyTransformDataCommand : ICommand - { - public bool CopyOutFileRows { private get; set; } - - public BinderFileManagerCore FileManagerCore { private get; set; } - - public IEnumerable FileManagers { private get; set; } - - public Output Output { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public IEnumerable FileFacades { get; private set; } - - public void Execute() - { - Debug.Assert(OutputType.Patch != this.Output.Type); - - List allFileRows = this.CopyOutFileRows ? new List() : null; - -#if false // TODO: Fix this patching related code to work correctly with FileFacades. - bool copyToPatch = (allFileRows != null); - bool copyFromPatch = !copyToPatch; - - RowDictionary patchMediaRows = new RowDictionary(); - - Dictionary> patchMediaFileRows = new Dictionary>(); - - Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); - Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); - - if (copyFromPatch) - { - // index patch files by diskId+fileId - foreach (WixFileRow patchFileRow in patchFileTable.Rows) - { - int diskId = patchFileRow.DiskId; - RowDictionary mediaFileRows; - if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) - { - mediaFileRows = new RowDictionary(); - patchMediaFileRows.Add(diskId, mediaFileRows); - } - - mediaFileRows.Add(patchFileRow); - } - - Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); - patchMediaRows = new RowDictionary(patchMediaTable); - } - - // index paired transforms - Dictionary pairedTransforms = new Dictionary(); - foreach (SubStorage substorage in this.Output.SubStorages) - { - if (substorage.Name.StartsWith("#")) - { - pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data); - } - } - - try - { - // copy File bind data into substorages - foreach (SubStorage substorage in this.Output.SubStorages) - { - if (substorage.Name.StartsWith("#")) - { - // no changes necessary for paired transforms - continue; - } - - Output mainTransform = substorage.Data; - Table mainWixFileTable = mainTransform.Tables["WixFile"]; - Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; - - this.FileManagerCore.ActiveSubStorage = substorage; - - RowDictionary mainWixFiles = new RowDictionary(mainWixFileTable); - RowDictionary mainMsiFileHashIndex = new RowDictionary(); - - Table mainFileTable = mainTransform.Tables["File"]; - Output pairedTransform = (Output)pairedTransforms[substorage.Name]; - - // copy Media.LastSequence and index the MsiFileHash table if it exists. - if (copyFromPatch) - { - Table pairedMediaTable = pairedTransform.Tables["Media"]; - foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) - { - MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); - pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; - } - - if (null != mainMsiFileHashTable) - { - mainMsiFileHashIndex = new RowDictionary(mainMsiFileHashTable); - } - - // Validate file row changes for keypath-related issues - this.ValidateFileRowChanges(mainTransform); - } - - // Index File table of pairedTransform - Table pairedFileTable = pairedTransform.Tables["File"]; - RowDictionary pairedFileRows = new RowDictionary(pairedFileTable); - - if (null != mainFileTable) - { - if (copyFromPatch) - { - // 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) - { - if (RowOperation.Delete == mainFileRow.Operation) - { - continue; - } - else if (RowOperation.None == mainFileRow.Operation && !copyToPatch) - { - continue; - } - - WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File); - - if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. - { - ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6]; - FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File); - - // If the file is new, we always need to add it to the patch. - if (mainFileRow.Operation != RowOperation.Add) - { - // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. - if (null == objectField.PreviousData) - { - if (mainFileRow.Operation == RowOperation.None) - { - continue; - } - } - else - { - // TODO: should this entire condition be placed in the binder file manager? - if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && - !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) - { - // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. - mainFileRow.Operation = RowOperation.Modify; - if (null != pairedFileRow) - { - // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = RowOperation.Modify; - } - } - else - { - // The File is same. We need mark all the attributes as unchanged. - mainFileRow.Operation = RowOperation.None; - foreach (Field field in mainFileRow.Fields) - { - field.Modified = false; - } - - if (null != pairedFileRow) - { - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded; - pairedFileRow.Fields[6].Modified = false; - pairedFileRow.Operation = RowOperation.None; - } - continue; - } - } - } - else if (null != pairedFileRow) // RowOperation.Add - { - // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = RowOperation.Add; - } - } - - // index patch files by diskId+fileId - int diskId = mainWixFileRow.DiskId; - - RowDictionary mediaFileRows; - if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) - { - mediaFileRows = new RowDictionary(); - patchMediaFileRows.Add(diskId, mediaFileRows); - } - - string fileId = mainFileRow.File; - WixFileRow patchFileRow = mediaFileRows.Get(fileId); - if (copyToPatch) - { - if (null == patchFileRow) - { - FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); - patchActualFileRow.CopyFrom(mainFileRow); - - patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); - patchFileRow.CopyFrom(mainWixFileRow); - - mediaFileRows.Add(patchFileRow); - - allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right? - } - else - { - // TODO: confirm the rest of data is identical? - - // make sure Source is same. Otherwise we are silently ignoring a file. - if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) - { - Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); - } - - // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. - patchFileRow.AppendPreviousDataFrom(mainWixFileRow); - } - } - else - { - // copy data from the patch back to the transform - if (null != patchFileRow) - { - FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId); - for (int i = 0; i < patchFileRow.Fields.Length; i++) - { - string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString(); - string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString(); - - 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). - } - // File.Attributes should not changed for binary deltas - else if (6 == i) - { - if (null != patchFileRow.Patch) - { - // File.Attribute should not change for binary deltas - pairedFileRow.Attributes = mainFileRow.Attributes; - mainFileRow.Fields[i].Modified = false; - } - } - // File.Sequence is updated in pairedTransform, not mainTransform - else if (7 == i) - { - // file sequence is updated in Patch table instead of File table for delta patches - if (null != patchFileRow.Patch) - { - pairedFileRow.Fields[i].Modified = false; - } - else - { - 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; - } - } - } - - // copy MsiFileHash row for this File - Row patchHashRow; - if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow)) - { - patchHashRow = patchFileRow.Hash; - } - - if (null != patchHashRow) - { - Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); - Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); - for (int 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; - } - - // copy MsiAssemblyName rows for this File - List patchAssemblyNameRows = patchFileRow.AssemblyNames; - if (null != patchAssemblyNameRows) - { - Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - foreach (Row patchAssemblyNameRow in patchAssemblyNameRows) - { - // Copy if there isn't an identical modified/added row already in the transform. - bool foundMatchingModifiedRow = false; - foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows) - { - if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) - { - foundMatchingModifiedRow = true; - break; - } - } - - if (!foundMatchingModifiedRow) - { - Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); - for (int 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; - } - } - } - - // Add patch header for this file - if (null != patchFileRow.Patch) - { - // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. - AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); - AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); - - // Add to Patch table - Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); - if (0 == patchTable.Rows.Count) - { - patchTable.Operation = TableOperation.Add; - } - - Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); - patchRow[0] = patchFileRow.File; - patchRow[1] = patchFileRow.Sequence; - - FileInfo patchFile = new FileInfo(patchFileRow.Source); - patchRow[2] = (int)patchFile.Length; - patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; - - string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; - if (MsiInterop.MsiMaxStreamNameLength < streamName.Length) - { - streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); - Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); - if (0 == patchHeadersTable.Rows.Count) - { - patchHeadersTable.Operation = TableOperation.Add; - } - Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); - patchHeadersRow[0] = streamName; - patchHeadersRow[1] = patchFileRow.Patch; - patchRow[5] = streamName; - patchHeadersRow.Operation = RowOperation.Add; - } - else - { - patchRow[4] = patchFileRow.Patch; - } - patchRow.Operation = RowOperation.Add; - } - } - else - { - // TODO: throw because all transform rows should have made it into the patch - } - } - } - } - - if (copyFromPatch) - { - this.Output.Tables.Remove("Media"); - this.Output.Tables.Remove("File"); - this.Output.Tables.Remove("MsiFileHash"); - this.Output.Tables.Remove("MsiAssemblyName"); - } - } - } - finally - { - this.FileManagerCore.ActiveSubStorage = null; - } -#endif - this.FileFacades = allFileRows; - } - - /// - /// Adds the PatchFiles action to the sequence table if it does not already exist. - /// - /// The sequence table to check or modify. - /// The primary authoring transform. - /// The secondary patch transform. - /// The file row that contains information about the patched file. - private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow) - { - // Find/add PatchFiles action (also determine sequence for it). - // Search mainTransform first, then pairedTransform (pairedTransform overrides). - bool hasPatchFilesAction = false; - int seqInstallFiles = 0; - int seqDuplicateFiles = 0; - string tableName = table.ToString(); - - TestSequenceTableForPatchFilesAction( - mainTransform.Tables[tableName], - ref hasPatchFilesAction, - ref seqInstallFiles, - ref seqDuplicateFiles); - TestSequenceTableForPatchFilesAction( - pairedTransform.Tables[tableName], - ref hasPatchFilesAction, - ref seqInstallFiles, - ref seqDuplicateFiles); - if (!hasPatchFilesAction) - { - Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); - if (0 == iesTable.Rows.Count) - { - iesTable.Operation = TableOperation.Add; - } - - Row patchAction = iesTable.CreateRow(null); - WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"]; - int sequence = wixPatchAction.Sequence; - // Test for default sequence value's appropriateness - if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence)) - { - if (0 != seqDuplicateFiles) - { - if (seqDuplicateFiles < seqInstallFiles) - { - throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); - } - else - { - sequence = (seqDuplicateFiles + seqInstallFiles) / 2; - if (seqInstallFiles == sequence || seqDuplicateFiles == sequence) - { - throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); - } - } - } - else - { - sequence = seqInstallFiles + 1; - } - } - patchAction[0] = wixPatchAction.Action; - patchAction[1] = wixPatchAction.Condition; - patchAction[2] = sequence; - patchAction.Operation = RowOperation.Add; - } - } - - /// - /// Tests sequence table for PatchFiles and associated actions - /// - /// The table to test. - /// Set to true if PatchFiles action is found. Left unchanged otherwise. - /// Set to sequence value of InstallFiles action if found. Left unchanged otherwise. - /// Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise. - private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles) - { - if (null != iesTable) - { - foreach (Row iesRow in iesTable.Rows) - { - if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal)) - { - hasPatchFilesAction = true; - } - if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal)) - { - seqInstallFiles = (int)iesRow.Fields[2].Data; - } - if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal)) - { - seqDuplicateFiles = (int)iesRow.Fields[2].Data; - } - } - } - } - - /// - /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. - /// - /// The output to validate. - private void ValidateFileRowChanges(Output transform) - { - Table componentTable = transform.Tables["Component"]; - Table fileTable = transform.Tables["File"]; - - // There's no sense validating keypaths if the transform has no component or file table - if (componentTable == null || fileTable == null) - { - return; - } - - Dictionary componentKeyPath = new Dictionary(componentTable.Rows.Count); - - // Index the Component table for non-directory & non-registry key paths. - foreach (Row row in componentTable.Rows) - { - if (null != row.Fields[5].Data && - 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) - { - componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString()); - } - } - - Dictionary componentWithChangedKeyPath = new Dictionary(); - Dictionary componentWithNonKeyPathChanged = new Dictionary(); - // Verify changes in the file table, now that file diffing has occurred - foreach (FileRow row in fileTable.Rows) - { - string fileId = row.Fields[0].Data.ToString(); - string componentId = row.Fields[1].Data.ToString(); - - if (RowOperation.Modify != row.Operation) - { - continue; - } - - // If this file is the keypath of a component - if (componentKeyPath.ContainsValue(fileId)) - { - if (!componentWithChangedKeyPath.ContainsKey(componentId)) - { - componentWithChangedKeyPath.Add(componentId, fileId); - } - } - else - { - if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) - { - componentWithNonKeyPathChanged.Add(componentId, fileId); - } - } - } - - foreach (KeyValuePair componentFile in componentWithNonKeyPathChanged) - { - // Make sure all changes to non keypath files also had a change in the keypath. - if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key)) - { - Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key])); - } - } - } - - private bool CompareFiles(string targetFile, string updatedFile) - { - bool? compared = null; - foreach (IBinderFileManager fileManager in this.FileManagers) - { - compared = fileManager.CompareFiles(targetFile, updatedFile); - if (compared.HasValue) - { - break; - } - } - - if (!compared.HasValue) - { - throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. - } - - return compared.Value; - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs deleted file mode 100644 index 35c8abb4..00000000 --- a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs +++ /dev/null @@ -1,489 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Threading; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - - /// - /// Creates cabinet files. - /// - internal class CreateCabinetsCommand : ICommand - { - private List fileTransfers; - - private FileSplitCabNamesCallback newCabNamesCallBack; - - private Dictionary lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence - - public CreateCabinetsCommand() - { - this.fileTransfers = new List(); - - this.newCabNamesCallBack = NewCabNamesCallBack; - } - - /// - /// Sets the number of threads to use for cabinet creation. - /// - public int CabbingThreadCount { private get; set; } - - public string TempFilesLocation { private get; set; } - - /// - /// Sets the default compression level to use for cabinets - /// that don't have their compression level explicitly set. - /// - public CompressionLevel DefaultCompressionLevel { private get; set; } - - public Output Output { private get; set; } - - public IEnumerable FileManagers { private get; set; } - - public string LayoutDirectory { private get; set; } - - public bool Compressed { private get; set; } - - public Dictionary> FileRowsByCabinet { private get; set; } - - public Func ResolveMedia { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public Table WixMediaTable { private get; set; } - - public IEnumerable FileTransfers { get { return this.fileTransfers; } } - - /// Output to generate image for. - /// Array of files to be transfered. - /// The directory in which the image should be layed out. - /// Flag if source image should be compressed. - /// The uncompressed file rows. - public void Execute() - { - RowDictionary wixMediaRows = new RowDictionary(this.WixMediaTable); - - this.lastCabinetAddedToMediaTable = new Dictionary(); - - this.SetCabbingThreadCount(); - - // Send Binder object to Facilitate NewCabNamesCallBack Callback - CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); - - // Supply Compile MediaTemplate Attributes to Cabinet Builder - int MaximumCabinetSizeForLargeFileSplitting; - int MaximumUncompressedMediaSize; - this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize); - cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting; - cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize; - - foreach (var entry in this.FileRowsByCabinet) - { - MediaRow mediaRow = entry.Key; - IEnumerable files = entry.Value; - CompressionLevel compressionLevel = this.DefaultCompressionLevel; - - WixMediaRow wixMediaRow = null; - string mediaLayoutFolder = null; - - if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) - { - mediaLayoutFolder = wixMediaRow.Layout; - - if (wixMediaRow.CompressionLevel.HasValue) - { - compressionLevel = wixMediaRow.CompressionLevel.Value; - } - } - - string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); - - CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers); - if (null != cabinetWorkItem) - { - cabinetBuilder.Enqueue(cabinetWorkItem); - } - } - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - - // create queued cabinets with multiple threads - cabinetBuilder.CreateQueuedCabinets(); - if (Messaging.Instance.EncounteredError) - { - return; - } - } - - /// - /// Sets the thead count to the number of processors if the current thread count is set to 0. - /// - /// The thread count value must be greater than 0 otherwise and exception will be thrown. - private void SetCabbingThreadCount() - { - // default the number of cabbing threads to the number of processors if it wasn't specified - if (0 == this.CabbingThreadCount) - { - string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); - - try - { - if (null != numberOfProcessors) - { - this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat); - - if (0 >= this.CabbingThreadCount) - { - throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); - } - } - else // default to 1 if the environment variable is not set - { - this.CabbingThreadCount = 1; - } - - Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); - } - catch (ArgumentException) - { - throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); - } - catch (FormatException) - { - throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); - } - } - } - - - /// - /// Creates a work item to create a cabinet. - /// - /// Output for the current database. - /// Directory to create cabinet in. - /// MediaRow containing information about the cabinet. - /// Collection of files in this cabinet. - /// Array of files to be transfered. - /// created CabinetWorkItem object - private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable fileFacades, List fileTransfers) - { - CabinetWorkItem cabinetWorkItem = null; - string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); - - // check for an empty cabinet - if (!fileFacades.Any()) - { - string cabinetName = mediaRow.Cabinet; - - // remove the leading '#' from the embedded cabinet name to make the warning easier to understand - if (cabinetName.StartsWith("#", StringComparison.Ordinal)) - { - cabinetName = cabinetName.Substring(1); - } - - // If building a patch, remind them to run -p for torch. - if (OutputType.Patch == output.Type) - { - Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true)); - } - else - { - Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName)); - } - } - - ResolvedCabinet resolvedCabinet = this.ResolveCabinet(tempCabinetFileX, fileFacades); - - // create a cabinet work item if it's not being skipped - if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) - { - int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet). - - cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/); - } - else // reuse the cabinet from the cabinet cache. - { - Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path)); - - try - { - // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The - // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that - // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from - // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) - // causing the project to look like it perpetually needs a rebuild until all of the reused - // cabinets get newer timestamps. - File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); - } - catch (Exception e) - { - Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message)); - } - } - - if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) - { - Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]); - - Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers); - streamRow[0] = mediaRow.Cabinet.Substring(1); - streamRow[1] = resolvedCabinet.Path; - } - else - { - string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); - FileTransfer transfer; - if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer)) - { - transfer.Built = true; - fileTransfers.Add(transfer); - } - } - - return cabinetWorkItem; - } - - private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) - { - ResolvedCabinet resolved = null; - - List filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); - - foreach (IBinderFileManager fileManager in this.FileManagers) - { - resolved = fileManager.ResolveCabinet(cabinetPath, filesWithPath); - if (null != resolved) - { - break; - } - } - - return resolved; - } - - /// - /// Delegate for Cabinet Split Callback - /// - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); - - /// - /// Call back to Add File Transfer for new Cab and add new Cab to Media table - /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe - /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored - /// - /// The name of splitting cabinet without extention e.g. "cab1". - /// The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab" - /// The file token of the first file present in the splitting cabinet - internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) - { - // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads - Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); - try - { - if (!mutex.WaitOne(0, false)) // Check if you can get the lock - { - // Cound not get the Lock - Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel()); - mutex.WaitOne(); // Wait on other thread - } - - string firstCabinetName = firstCabName + ".cab"; - string newCabinetName = newCabName; - bool transferAdded = false; // Used for Error Handling - - // Create File Transfer for new Cabinet using transfer of Base Cabinet - foreach (FileTransfer transfer in this.FileTransfers) - { - if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) - { - string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); - string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); - - FileTransfer newTransfer; - if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer)) - { - newTransfer.Built = true; - this.fileTransfers.Add(newTransfer); - transferAdded = true; - break; - } - } - } - - // Check if File Transfer was added - if (!transferAdded) - { - throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); - } - - // Add the new Cabinets to media table using LastSequence of Base Cabinet - Table mediaTable = this.Output.Tables["Media"]; - Table wixFileTable = this.Output.Tables["WixFile"]; - int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain - int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain - bool lastSplitCabinetFound = false; // Used for Error Handling - - string lastCabinetOfThisSequence = String.Empty; - // Get the Value of Last Cabinet Added in this split Sequence from Dictionary - if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) - { - // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence - lastCabinetOfThisSequence = firstCabinetName; - } - - foreach (MediaRow mediaRow in mediaTable.Rows) - { - // Get details for the Last Cabinet Added in this Split Sequence - if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; - diskIDForLastSplitCabAdded = mediaRow.DiskId; - lastSplitCabinetFound = true; - } - - // Check for Name Collision for the new Cabinet added - if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row - throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); - } - } - - // Check if the last Split Cabinet was found in the Media Table - if (!lastSplitCabinetFound) - { - throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); - } - - // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort - // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with - // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction - MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); - newMediaRow.Cabinet = newCabinetName; - newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion - newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; - - // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique - foreach (MediaRow mediaRow in mediaTable.Rows) - { - // Check if this row comes after inserted row and it is not the new cabinet inserted row - if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - mediaRow.DiskId++; // Increment DiskID - } - } - - // Now Increment DiskID for All files Rows so that they refer to the right Media Row - foreach (WixFileRow wixFileRow in wixFileTable.Rows) - { - // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet - // This check will work as we have only one large file in every splitting cabinet - // If we want to support splitting cabinet with more large files we need to update this code - if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) - { - wixFileRow.DiskId++; // Increment DiskID - } - } - - // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback - this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; - - mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails - } - finally - { - // Releasing the Mutex here - mutex.ReleaseMutex(); - } - } - - - /// - /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides - /// - /// Output to generate image for. - /// The indexed file rows. - private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) - { - // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size - string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); - string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); - int maxCabSizeForLargeFileInMB = 0; - int maxPreCompressedSizeInMB = 0; - ulong testOverFlow = 0; - - // Supply Compile MediaTemplate Attributes to Cabinet Builder - Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; - if (mediaTemplateTable != null) - { - WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; - - // Get the Value for Max Cab Size for File Splitting - try - { - // Override authored mcslfs value if environment variable is authored. - if (!String.IsNullOrEmpty(mcslfsString)) - { - maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString); - } - else - { - maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting; - } - testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; - } - catch (FormatException) - { - throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); - } - catch (OverflowException) - { - throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting)); - } - - try - { - // Override authored mums value if environment variable is authored. - if (!String.IsNullOrEmpty(mumsString)) - { - maxPreCompressedSizeInMB = Int32.Parse(mumsString); - } - else - { - maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; - } - testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; - } - catch (FormatException) - { - throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); - } - catch (OverflowException) - { - throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); - } - - maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; - maxUncompressedMediaSize = maxPreCompressedSizeInMB; - } - else - { - maxCabSizeForLargeFileSplitting = 0; - maxUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs deleted file mode 100644 index 933a1ea8..00000000 --- a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs +++ /dev/null @@ -1,86 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.Rows; - - /// - /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. - /// - internal class CreateDeltaPatchesCommand : ICommand - { - public IEnumerable FileFacades { private get; set; } - - public Table WixPatchIdTable { private get; set; } - - public string TempFilesLocation { private get; set; } - - public void Execute() - { - bool optimizePatchSizeForLargeFiles = false; - PatchAPI.PatchInterop.PatchSymbolFlagsType apiPatchingSymbolFlags = 0; - - if (null != this.WixPatchIdTable) - { - Row row = this.WixPatchIdTable.Rows[0]; - if (null != row) - { - if (null != row[2]) - { - optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture)); - } - - if (null != row[3]) - { - apiPatchingSymbolFlags = (PatchAPI.PatchInterop.PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture); - } - } - } - - foreach (FileFacade facade in this.FileFacades) - { - if (RowOperation.Modify == facade.File.Operation && - 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) - { - string deltaBase = String.Concat("delta_", facade.File.File); - string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf")); - string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd")); - - bool retainRangeWarning = false; - - if (PatchAPI.PatchInterop.CreateDelta( - deltaFile, - facade.WixFile.Source, - facade.DeltaPatchFile.Symbols, - facade.DeltaPatchFile.RetainOffsets, - new[] { facade.WixFile.PreviousSource }, - facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), - apiPatchingSymbolFlags, - optimizePatchSizeForLargeFiles, - out retainRangeWarning)) - { - PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); - - facade.WixFile.Source = deltaFile; - facade.WixFile.DeltaPatchHeaderSource = headerFile; - } - - if (retainRangeWarning) - { - // TODO: get patch family to add to warning message for PatchWiz parity. - Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); - } - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs deleted file mode 100644 index 5db2768b..00000000 --- a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs +++ /dev/null @@ -1,68 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class CreateSpecialPropertiesCommand : ICommand - { - public Table PropertyTable { private get; set; } - - public Table WixPropertyTable { private get; set; } - - public void Execute() - { - // Create the special properties. - if (null != this.WixPropertyTable) - { - // Create lists of the properties that contribute to the special lists of properties. - SortedSet adminProperties = new SortedSet(); - SortedSet secureProperties = new SortedSet(); - SortedSet hiddenProperties = new SortedSet(); - - foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows) - { - if (wixPropertyRow.Admin) - { - adminProperties.Add(wixPropertyRow.Id); - } - - if (wixPropertyRow.Hidden) - { - hiddenProperties.Add(wixPropertyRow.Id); - } - - if (wixPropertyRow.Secure) - { - secureProperties.Add(wixPropertyRow.Id); - } - } - - Table propertyTable = this.PropertyTable; - if (0 < adminProperties.Count) - { - PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); - row.Property = "AdminProperties"; - row.Value = String.Join(";", adminProperties); - } - - if (0 < secureProperties.Count) - { - PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); - row.Property = "SecureCustomProperties"; - row.Value = String.Join(";", secureProperties); - } - - if (0 < hiddenProperties.Count) - { - PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); - row.Property = "MsiHiddenProperties"; - row.Value = String.Join(";", hiddenProperties); - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs deleted file mode 100644 index bee1488b..00000000 --- a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs +++ /dev/null @@ -1,225 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using WixToolset.Cab; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.MergeMod; - using WixToolset.Msi; - using WixToolset.Core.Native; - - /// - /// Retrieve files information and extract them from merge modules. - /// - internal class ExtractMergeModuleFilesCommand : ICommand - { - public IEnumerable FileFacades { private get; set; } - - public Table FileTable { private get; set; } - - public Table WixFileTable { private get; set; } - - public Table WixMergeTable { private get; set; } - - public int OutputInstallerVersion { private get; set; } - - public bool SuppressLayout { private get; set; } - - public string TempFilesLocation { private get; set; } - - public IEnumerable MergeModulesFileFacades { get; private set; } - - public void Execute() - { - List mergeModulesFileFacades = new List(); - - IMsmMerge2 merge = MsmInterop.GetMsmMerge(); - - // Index all of the file rows to be able to detect collisions with files in the Merge Modules. - // It may seem a bit expensive to build up this index solely for the purpose of checking collisions - // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out - // there are other cases where we need all the file rows indexed, however they are not common cases. - // 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. - Dictionary indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal); - - foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows) - { - bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); - - // If the module has files and creating layout - if (containsFiles && !this.SuppressLayout) - { - this.ExtractFilesFromMergeModule(merge, wixMergeRow); - } - } - - this.MergeModulesFileFacades = mergeModulesFileFacades; - } - - private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List mergeModulesFileFacades, Dictionary indexedFileFacades) - { - bool containsFiles = false; - - try - { - // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. - using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) - { - if (db.TableExists("File") && db.TableExists("Component")) - { - Dictionary uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); - - using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) - { - // add each file row from the merge module into the file row collection (check for errors along the way) - while (true) - { - using (Record record = view.Fetch()) - { - if (null == record) - { - break; - } - - // NOTE: this is very tricky - the merge module file rows are not added to the - // file table because they should not be created via idt import. Instead, these - // rows are created by merging in the actual modules. - FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); - fileRow.File = record[1]; - fileRow.Compressed = wixMergeRow.FileCompression; - - WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); - wixFileRow.Directory = record[2]; - wixFileRow.DiskId = wixMergeRow.DiskId; - wixFileRow.PatchGroup = -1; - wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]); - - FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow); - - FileFacade collidingFacade; - - // If case-sensitive collision with another merge module or a user-authored file identifier. - if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) - { - Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File)); - } - else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module - { - Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File)); - } - else // no collision - { - mergeModulesFileFacades.Add(mergeModuleFileFacade); - - // Keep updating the indexes as new rows are added. - indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); - uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); - } - - containsFiles = true; - } - } - } - } - - // Get the summary information to detect the Schema - using (SummaryInformation summaryInformation = new SummaryInformation(db)) - { - string moduleInstallerVersionString = summaryInformation.GetProperty(14); - - try - { - int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); - if (moduleInstallerVersion > this.OutputInstallerVersion) - { - Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion)); - } - } - catch (FormatException) - { - throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); - } - } - } - } - catch (FileNotFoundException) - { - throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); - } - catch (Win32Exception) - { - throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); - } - - return containsFiles; - } - - private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow) - { - bool moduleOpen = false; - short mergeLanguage; - - try - { - mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); - } - catch (System.FormatException) - { - Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); - return; - } - - try - { - merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); - moduleOpen = true; - - string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat); - - // extract the module cabinet, then explode all of the files to a temp directory - string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); - merge.ExtractCAB(moduleCabPath); - - string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); - Directory.CreateDirectory(mergeIdPath); - - using (WixExtractCab extractCab = new WixExtractCab()) - { - try - { - extractCab.Extract(moduleCabPath, mergeIdPath); - } - catch (FileNotFoundException) - { - throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); - } - catch - { - throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); - } - } - } - catch (COMException ce) - { - throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); - } - finally - { - if (moduleOpen) - { - merge.CloseModule(); - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/FileFacade.cs b/src/WixToolset.Core/Bind/Databases/FileFacade.cs deleted file mode 100644 index 37115c97..00000000 --- a/src/WixToolset.Core/Bind/Databases/FileFacade.cs +++ /dev/null @@ -1,44 +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.Bind.Databases -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Rows; - - public class FileFacade - { - public FileFacade(FileRow file, WixFileRow wixFile, WixDeltaPatchFileRow deltaPatchFile) - { - this.File = file; - this.WixFile = wixFile; - this.DeltaPatchFile = deltaPatchFile; - } - - public FileFacade(bool fromModule, FileRow file, WixFileRow wixFile) - { - this.FromModule = fromModule; - this.File = file; - this.WixFile = wixFile; - } - - public bool FromModule { get; private set; } - - public FileRow File { get; private set; } - - public WixFileRow WixFile { get; private set; } - - public WixDeltaPatchFileRow DeltaPatchFile { get; private set; } - - /// - /// 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 Row Hash { get; set; } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs deleted file mode 100644 index b6bcd3af..00000000 --- a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs +++ /dev/null @@ -1,148 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class GetFileFacadesCommand : ICommand - { - public Table FileTable { private get; set; } - - public Table WixFileTable { private get; set; } - - public Table WixDeltaPatchFileTable { private get; set; } - - public Table WixDeltaPatchSymbolPathsTable { private get; set; } - - public List FileFacades { get; private set; } - - public void Execute() - { - List facades = new List(this.FileTable.Rows.Count); - - RowDictionary wixFiles = new RowDictionary(this.WixFileTable); - RowDictionary deltaPatchFiles = new RowDictionary(this.WixDeltaPatchFileTable); - - foreach (FileRow file in this.FileTable.Rows) - { - WixDeltaPatchFileRow deltaPatchFile = null; - - deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile); - - facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile)); - } - - if (null != this.WixDeltaPatchSymbolPathsTable) - { - this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); - } - - this.FileFacades = facades; - } - - /// - /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. - /// - public RowDictionary ResolveDeltaPatchSymbolPaths(RowDictionary deltaPatchFiles, IEnumerable facades) - { - ILookup filesByComponent = null; - ILookup filesByDirectory = null; - ILookup filesByDiskId = null; - - foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs().OrderBy(r => r.Type)) - { - switch (row.Type) - { - case SymbolPathType.File: - this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]); - break; - - case SymbolPathType.Component: - if (null == filesByComponent) - { - filesByComponent = facades.ToLookup(f => f.File.Component); - } - - foreach (FileFacade facade in filesByComponent[row.Id]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); - } - break; - - case SymbolPathType.Directory: - if (null == filesByDirectory) - { - filesByDirectory = facades.ToLookup(f => f.WixFile.Directory); - } - - foreach (FileFacade facade in filesByDirectory[row.Id]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); - } - break; - - case SymbolPathType.Media: - if (null == filesByDiskId) - { - filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture)); - } - - foreach (FileFacade facade in filesByDiskId[row.Id]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); - } - break; - - case SymbolPathType.Product: - foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values) - { - this.MergeSymbolPaths(row, fileRow); - } - break; - - default: - // error - break; - } - } - - return deltaPatchFiles; - } - - /// - /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. - /// - /// Row from the WixPatchSymbolsPaths table. - /// FileRow into which to set symbol information. - /// This includes PreviousData as well. - private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file) - { - if (null == file.Symbols) - { - file.Symbols = row.SymbolPaths; - } - else - { - file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths); - } - - Field field = row.Fields[2]; - if (null != field.PreviousData) - { - if (null == file.PreviousSymbols) - { - file.PreviousSymbols = field.PreviousData; - } - else - { - file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs deleted file mode 100644 index 035ef059..00000000 --- a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs +++ /dev/null @@ -1,350 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using System.Xml; - using System.Xml.XPath; - using WixToolset.Clr.Interop; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.MergeMod; - using WixToolset.Msi; - using WixToolset.Core.Native; - - /// - /// Update file information. - /// - internal class MergeModulesCommand : ICommand - { - public IEnumerable FileFacades { private get; set; } - - public Output Output { private get; set; } - - public string OutputPath { private get; set; } - - public IEnumerable SuppressedTableNames { private get; set; } - - public string TempFilesLocation { private get; set; } - - public void Execute() - { - Debug.Assert(OutputType.Product == this.Output.Type); - - Table wixMergeTable = this.Output.Tables["WixMerge"]; - Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"]; - - // check for merge rows to see if there is any work to do - if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) - { - return; - } - - IMsmMerge2 merge = null; - bool commit = true; - bool logOpen = false; - bool databaseOpen = false; - string logPath = null; - try - { - merge = MsmInterop.GetMsmMerge(); - - logPath = Path.Combine(this.TempFilesLocation, "merge.log"); - merge.OpenLog(logPath); - logOpen = true; - - merge.OpenDatabase(this.OutputPath); - databaseOpen = true; - - // process all the merge rows - foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) - { - bool moduleOpen = false; - - try - { - short mergeLanguage; - - try - { - mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); - } - catch (System.FormatException) - { - Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); - continue; - } - - Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); - merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); - moduleOpen = true; - - // If there is merge configuration data, create a callback object to contain it all. - ConfigurationCallback callback = null; - if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) - { - callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); - } - - // merge the module into the database that's being built - Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); - merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); - - // connect any non-primary features - if (null != wixFeatureModulesTable) - { - foreach (Row row in wixFeatureModulesTable.Rows) - { - if (wixMergeRow.Id == (string)row[1]) - { - Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); - merge.Connect((string)row[0]); - } - } - } - } - catch (COMException) - { - commit = false; - } - finally - { - IMsmErrors mergeErrors = merge.Errors; - - // display all the errors encountered during the merge operations for this module - for (int i = 1; i <= mergeErrors.Count; i++) - { - IMsmError mergeError = mergeErrors[i]; - StringBuilder databaseKeys = new StringBuilder(); - StringBuilder moduleKeys = new StringBuilder(); - - // build a string of the database keys - for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) - { - if (1 != j) - { - databaseKeys.Append(';'); - } - databaseKeys.Append(mergeError.DatabaseKeys[j]); - } - - // build a string of the module keys - for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) - { - if (1 != j) - { - moduleKeys.Append(';'); - } - moduleKeys.Append(mergeError.ModuleKeys[j]); - } - - // display the merge error based on the msm error type - switch (mergeError.Type) - { - case MsmErrorType.msmErrorExclusion: - Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); - break; - case MsmErrorType.msmErrorFeatureRequired: - Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); - break; - case MsmErrorType.msmErrorLanguageFailed: - Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorLanguageUnsupported: - Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorResequenceMerge: - Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorTableMerge: - if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table - { - Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); - } - break; - case MsmErrorType.msmErrorPlatformMismatch: - Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); - break; - default: - Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); - break; - } - } - - if (0 >= mergeErrors.Count && !commit) - { - Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); - } - - if (moduleOpen) - { - merge.CloseModule(); - } - } - } - } - finally - { - if (databaseOpen) - { - merge.CloseDatabase(commit); - } - - if (logOpen) - { - merge.CloseLog(); - } - } - - // stop processing if an error previously occurred - if (Messaging.Instance.EncounteredError) - { - return; - } - - using (Database db = new Database(this.OutputPath, OpenDatabase.Direct)) - { - Table suppressActionTable = this.Output.Tables["WixSuppressAction"]; - - // suppress individual actions - if (null != suppressActionTable) - { - foreach (Row row in suppressActionTable.Rows) - { - if (db.TableExists((string)row[0])) - { - string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); - - using (View view = db.OpenExecuteView(query)) - { - using (Record record = view.Fetch()) - { - if (null != record) - { - Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); - view.Modify(ModifyView.Delete, record); - } - } - } - } - } - } - - // query for merge module actions in suppressed sequences and drop them - foreach (string tableName in this.SuppressedTableNames) - { - if (!db.TableExists(tableName)) - { - continue; - } - - using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) - { - while (true) - { - using (Record resultRecord = view.Fetch()) - { - if (null == resultRecord) - { - break; - } - - Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); - } - } - } - - // drop suppressed sequences - using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) - { - } - - // delete the validation rows - using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) - { - using (Record record = new Record(1)) - { - record.SetString(1, tableName); - view.Execute(record); - } - } - } - - // now update the Attributes column for the files from the Merge Modules - Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); - using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) - { - foreach (FileFacade file in this.FileFacades) - { - if (!file.FromModule) - { - continue; - } - - using (Record record = new Record(1)) - { - record.SetString(1, file.File.File); - view.Execute(record); - } - - using (Record recordUpdate = view.Fetch()) - { - if (null == recordUpdate) - { - throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); - } - - recordUpdate.SetInteger(1, file.File.Sequence); - - // update the file attributes to match the compression specified - // on the Merge element or on the Package element - int attributes = 0; - - // get the current value if its not null - if (!recordUpdate.IsNull(2)) - { - attributes = recordUpdate.GetInteger(2); - } - - if (YesNoType.Yes == file.File.Compressed) - { - // these are mutually exclusive - attributes |= MsiInterop.MsidbFileAttributesCompressed; - attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; - } - else if (YesNoType.No == file.File.Compressed) - { - // these are mutually exclusive - attributes |= MsiInterop.MsidbFileAttributesNoncompressed; - attributes &= ~MsiInterop.MsidbFileAttributesCompressed; - } - else // not specified - { - Debug.Assert(YesNoType.NotSet == file.File.Compressed); - - // clear any compression bits - attributes &= ~MsiInterop.MsidbFileAttributesCompressed; - attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; - } - - recordUpdate.SetInteger(2, attributes); - - view.Modify(ModifyView.Update, recordUpdate); - } - } - } - - db.Commit(); - } - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs deleted file mode 100644 index dd7b85b7..00000000 --- a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs +++ /dev/null @@ -1,115 +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.Bind.Databases -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Msi; - using WixToolset.Core.Native; - - /// - /// Defines the file transfers necessary to layout the uncompressed files. - /// - internal class ProcessUncompressedFilesCommand : ICommand - { - public string DatabasePath { private get; set; } - - public IEnumerable FileFacades { private get; set; } - - public RowDictionary MediaRows { private get; set; } - - public string LayoutDirectory { private get; set; } - - public bool Compressed { private get; set; } - - public bool LongNamesInImage { private get; set; } - - public Func ResolveMedia { private get; set; } - - public Table WixMediaTable { private get; set; } - - public IEnumerable FileTransfers { get; private set; } - - public void Execute() - { - List fileTransfers = new List(); - - Hashtable directories = new Hashtable(); - - RowDictionary wixMediaRows = new RowDictionary(this.WixMediaTable); - - using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) - { - using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) - { - while (true) - { - using (Record directoryRecord = directoryView.Fetch()) - { - if (null == directoryRecord) - { - break; - } - - string sourceName = Installer.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); - - directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); - } - } - } - - using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) - { - using (Record fileQueryRecord = new Record(1)) - { - // for each file in the array of uncompressed files - foreach (FileFacade facade in this.FileFacades) - { - MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId); - string relativeFileLayoutPath = null; - - WixMediaRow wixMediaRow = null; - string mediaLayoutFolder = null; - - if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) - { - mediaLayoutFolder = wixMediaRow.Layout; - } - - string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); - - // setup up the query record and find the appropriate file in the - // previously executed file view - fileQueryRecord[1] = facade.File.File; - fileView.Execute(fileQueryRecord); - - using (Record fileRecord = fileView.Fetch()) - { - if (null == fileRecord) - { - throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File)); - } - - relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); - } - - // finally put together the base media layout path and the relative file layout path - string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); - FileTransfer transfer; - if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer)) - { - fileTransfers.Add(transfer); - } - } - } - } - } - - this.FileTransfers = fileTransfers; - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs deleted file mode 100644 index 9e17ee02..00000000 --- a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs +++ /dev/null @@ -1,80 +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.Bind.Databases -{ - using System; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.Rows; - - internal class UpdateControlTextCommand : ICommand - { - public Table BBControlTable { private get; set; } - - public Table WixBBControlTable { private get; set; } - - public Table ControlTable { private get; set; } - - public Table WixControlTable { private get; set; } - - public void Execute() - { - if (null != this.WixBBControlTable) - { - RowDictionary bbControlRows = new RowDictionary(this.BBControlTable); - foreach (Row wixRow in this.WixBBControlTable.Rows) - { - BBControlRow bbControlRow = bbControlRows.Get(wixRow.GetPrimaryKey()); - bbControlRow.Text = this.ReadTextFile(bbControlRow.SourceLineNumbers, wixRow.FieldAsString(2)); - } - } - - if (null != this.WixControlTable) - { - RowDictionary controlRows = new RowDictionary(this.ControlTable); - foreach (Row wixRow in this.WixControlTable.Rows) - { - ControlRow controlRow = controlRows.Get(wixRow.GetPrimaryKey()); - controlRow.Text = this.ReadTextFile(controlRow.SourceLineNumbers, wixRow.FieldAsString(2)); - } - } - } - - /// - /// Reads a text file and returns the contents. - /// - /// Source line numbers for row from source. - /// Source path to file to read. - /// Text string read from file. - private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source) - { - string text = null; - - try - { - using (StreamReader reader = new StreamReader(source)) - { - text = reader.ReadToEnd(); - } - } - catch (DirectoryNotFoundException e) - { - Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (FileNotFoundException e) - { - Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (IOException e) - { - Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (NotSupportedException) - { - Messaging.Instance.OnMessage(WixErrors.FileNotFound(sourceLineNumbers, source)); - } - - return text; - } - } -} diff --git a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs deleted file mode 100644 index 36818afa..00000000 --- a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs +++ /dev/null @@ -1,532 +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.Bind.Databases -{ - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Xml; - using System.Xml.XPath; - using WixToolset.Clr.Interop; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Msi; - - /// - /// Update file information. - /// - internal class UpdateFileFacadesCommand : ICommand - { - public IEnumerable FileFacades { private get; set; } - - public IEnumerable UpdateFileFacades { private get; set; } - - public string ModularizationGuid { private get; set; } - - public Output Output { private get; set; } - - public bool OverwriteHash { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public IDictionary VariableCache { private get; set; } - - public void Execute() - { - foreach (FileFacade file in this.UpdateFileFacades) - { - this.UpdateFileFacade(file); - } - } - - private void UpdateFileFacade(FileFacade file) - { - FileInfo fileInfo = null; - try - { - fileInfo = new FileInfo(file.WixFile.Source); - } - catch (ArgumentException) - { - Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); - return; - } - catch (PathTooLongException) - { - Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); - return; - } - catch (NotSupportedException) - { - Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); - return; - } - - if (!fileInfo.Exists) - { - Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source)); - return; - } - - using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - if (Int32.MaxValue < fileStream.Length) - { - throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source)); - } - - file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); - } - - string version = null; - string language = null; - try - { - Installer.GetFileVersion(fileInfo.FullName, out version, out language); - } - catch (Win32Exception e) - { - if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND - { - throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); - } - else - { - throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); - } - } - - // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. - if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table - { - if (!this.OverwriteHash) - { - // not overwriting hash, so don't do the rest of these options. - } - else if (null != file.File.Version) - { - // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks - // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up. - // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing - // all the file rows) for a relatively uncommon situation. Let's not do that. - // - // 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 => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) - { - Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File)); - } - } - else - { - if (null != file.File.Language) - { - Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); - } - - int[] hash; - try - { - Installer.GetFileHash(fileInfo.FullName, 0, out hash); - } - catch (Win32Exception e) - { - if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND - { - throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); - } - else - { - throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); - } - } - - if (null == file.Hash) - { - Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]); - file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers); - } - - file.Hash[0] = file.File.File; - file.Hash[1] = 0; - file.Hash[2] = hash[0]; - file.Hash[3] = hash[1]; - file.Hash[4] = hash[2]; - file.Hash[5] = hash[3]; - } - } - else // update the file row with the version and language information. - { - // If no version was provided by the user, use the version from the file itself. - // This is the most common case. - if (String.IsNullOrEmpty(file.File.Version)) - { - file.File.Version = version; - } - else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, 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 - // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match - // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case - // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more - // CPU intensive search to save on the memory intensive index that wouldn't be used much. - // - // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. - // That's typically even more rare than companion files so again, no index, just search. - file.File.Version = version; - } - - if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language)) - { - Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); - } - else // override the default provided by the user (usually nothing) with the actual language from the file itself. - { - file.File.Language = language; - } - - // Populate the binder variables for this file information if requested. - if (null != this.VariableCache) - { - if (!String.IsNullOrEmpty(file.File.Version)) - { - string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)); - this.VariableCache[key] = file.File.Version; - } - - if (!String.IsNullOrEmpty(file.File.Language)) - { - string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, ModularizationGuid, file.File.File)); - this.VariableCache[key] = file.File.Language; - } - } - } - - // If this is a CLR assembly, load the assembly and get the assembly name information - if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) - { - bool targetNetfx1 = false; - StringDictionary assemblyNameValues = new StringDictionary(); - - ClrInterop.IReferenceIdentity referenceIdentity = null; - Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; - uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity); - if (0 == result && null != referenceIdentity) - { - string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); - if (null != imageRuntimeVersion) - { - targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); - } - - string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral"; - assemblyNameValues.Add("Culture", culture); - - string name = referenceIdentity.GetAttribute(null, "Name"); - if (null != name) - { - assemblyNameValues.Add("Name", name); - } - - string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); - if (null != processorArchitecture) - { - assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); - } - - string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); - if (null != publicKeyToken) - { - bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); - - // Managed code expects "null" instead of "neutral", and - // this won't be installed to the GAC since it's not signed anyway. - assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); - assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); - } - else if (file.WixFile.AssemblyApplication == null) - { - throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component)); - } - - string assemblyVersion = referenceIdentity.GetAttribute(null, "Version"); - if (null != version) - { - assemblyNameValues.Add("Version", assemblyVersion); - } - } - else - { - Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); - return; - } - - Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - if (assemblyNameValues.ContainsKey("name")) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]); - } - - if (!String.IsNullOrEmpty(version)) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version); - } - - if (assemblyNameValues.ContainsKey("version")) - { - string assemblyVersion = assemblyNameValues["version"]; - - if (!targetNetfx1) - { - // There is a bug in v1 fusion that requires the assembly's "version" attribute - // to be equal to or longer than the "fileVersion" in length when its present; - // the workaround is to prepend zeroes to the last version number in the assembly - // version. - if (null != version && version.Length > assemblyVersion.Length) - { - string padding = new string('0', version.Length - assemblyVersion.Length); - string[] assemblyVersionNumbers = assemblyVersion.Split('.'); - - if (assemblyVersionNumbers.Length > 0) - { - assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); - assemblyVersion = String.Join(".", assemblyVersionNumbers); - } - } - } - - this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion); - } - - if (assemblyNameValues.ContainsKey("culture")) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]); - } - - if (assemblyNameValues.ContainsKey("publicKeyToken")) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); - } - - if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); - } - - if (assemblyNameValues.ContainsKey("processorArchitecture")) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); - } - - // add the assembly name to the information cache - if (null != this.VariableCache) - { - string fileId = BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File); - string key = String.Concat("assemblyfullname.", fileId); - string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); - if (assemblyNameValues.ContainsKey("processorArchitecture")) - { - assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); - } - - this.VariableCache[key] = assemblyName; - - // Add entries with the preserved case publicKeyToken - string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); - this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); - - string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); - this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; - } - } - else if (FileAssemblyType.Win32Assembly == file.WixFile.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. - FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal)); - if (null == fileManifest) - { - Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest)); - } - - string win32Type = null; - string win32Name = null; - string win32Version = null; - string win32ProcessorArchitecture = null; - string win32PublicKeyToken = null; - - // loading the dom is expensive we want more performant APIs than the DOM - // Navigator is cheaper than dom. Perhaps there is a cheaper API still. - try - { - XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source); - XPathNavigator nav = doc.CreateNavigator(); - nav.MoveToRoot(); - - // this assumes a particular schema for a win32 manifest and does not - // provide error checking if the file does not conform to schema. - // The fallback case here is that nothing is added to the MsiAssemblyName - // table for an out of tolerance Win32 manifest. Perhaps warnings needed. - if (nav.MoveToFirstChild()) - { - while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") - { - nav.MoveToNext(); - } - - if (nav.MoveToFirstChild()) - { - bool hasNextSibling = true; - while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) - { - hasNextSibling = nav.MoveToNext(); - } - if (!hasNextSibling) - { - Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source)); - return; - } - - if (nav.MoveToAttribute("type", String.Empty)) - { - win32Type = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("name", String.Empty)) - { - win32Name = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("version", String.Empty)) - { - win32Version = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("processorArchitecture", String.Empty)) - { - win32ProcessorArchitecture = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("publicKeyToken", String.Empty)) - { - win32PublicKeyToken = nav.Value; - nav.MoveToParent(); - } - } - } - } - catch (FileNotFoundException fe) - { - Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest")); - } - catch (XmlException xe) - { - Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message)); - } - - Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - if (!String.IsNullOrEmpty(win32Name)) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name); - } - - if (!String.IsNullOrEmpty(win32Version)) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version); - } - - if (!String.IsNullOrEmpty(win32Type)) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type); - } - - if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture); - } - - if (!String.IsNullOrEmpty(win32PublicKeyToken)) - { - this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken); - } - } - } - - /// - /// 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(Table assemblyNameTable, FileFacade file, 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)) - { - Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name)); - } - else - { - Row assemblyNameRow = null; - - // override directly authored value - foreach (Row row in assemblyNameTable.Rows) - { - if ((string)row[0] == file.File.Component && (string)row[1] == name) - { - assemblyNameRow = row; - break; - } - } - - // 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 && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType && - String.IsNullOrEmpty(file.WixFile.AssemblyApplication) && - !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase)) - { - Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value)); - } - - if (null == assemblyNameRow) - { - assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers); - assemblyNameRow[0] = file.File.Component; - assemblyNameRow[1] = name; - assemblyNameRow[2] = value; - - // put the MsiAssemblyName row in the same section as the related File row - assemblyNameRow.SectionId = file.File.SectionId; - - if (null == file.AssemblyNames) - { - file.AssemblyNames = new List(); - } - - file.AssemblyNames.Add(assemblyNameRow); - } - else - { - assemblyNameRow[2] = value; - } - - if (this.VariableCache != null) - { - string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant(); - this.VariableCache[key] = (string)assemblyNameRow[2]; - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/DelayedField.cs b/src/WixToolset.Core/Bind/DelayedField.cs index 181ac3e3..6c56f27c 100644 --- a/src/WixToolset.Core/Bind/DelayedField.cs +++ b/src/WixToolset.Core/Bind/DelayedField.cs @@ -1,18 +1,15 @@ // 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.Bind +namespace WixToolset.Core.Bind { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; using WixToolset.Data; + using WixToolset.Extensibility; /// /// Structure used to hold a row and field that contain binder variables, which need to be resolved /// later, once the files have been resolved. /// - internal class DelayedField + internal class DelayedField : IDelayedField { /// /// Basic constructor for struct @@ -28,11 +25,11 @@ namespace WixToolset.Bind /// /// The row containing the field. /// - public Row Row { get; private set; } + public Row Row { get; } /// /// The field needing further resolving. /// - public Field Field { get; private set; } + public Field Field { get; } } } diff --git a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs new file mode 100644 index 00000000..fc2b43c7 --- /dev/null +++ b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs @@ -0,0 +1,16 @@ +// 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 WixToolset.Extensibility; + + internal class ExpectedExtractFile : IExpectedExtractFile + { + public Uri Uri { get; set; } + + public int EmbeddedFileIndex { get; set; } + + public string OutputPath { get; set; } + } +} diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs index 0ecd0096..28fc4817 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs @@ -1,6 +1,6 @@ // 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.Bind +namespace WixToolset.Core.Bind { using System; using System.Collections.Generic; @@ -13,11 +13,11 @@ namespace WixToolset.Bind /// /// Internal helper class used to extract embedded files. /// - internal sealed class ExtractEmbeddedFiles + internal class ExtractEmbeddedFiles { private Dictionary> filesWithEmbeddedFiles = new Dictionary>(); - public IEnumerable Uris { get { return this.filesWithEmbeddedFiles.Keys; } } + public IEnumerable Uris => this.filesWithEmbeddedFiles.Keys; /// /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. @@ -53,15 +53,30 @@ namespace WixToolset.Bind return extractPath; } - public IEnumerable GetExtractFilesForUri(Uri uri) + public IEnumerable GetExpectedEmbeddedFiles() { - SortedList extracts; - if (!filesWithEmbeddedFiles.TryGetValue(uri, out extracts)) + foreach (var uriWithExtracts in filesWithEmbeddedFiles) + { + foreach (var extracts in uriWithExtracts.Value) + { + yield return new ExpectedExtractFile + { + Uri = uriWithExtracts.Key, + EmbeddedFileIndex = extracts.Key, + OutputPath = extracts.Value, + }; + } + } + } + + public IEnumerable GetExtractFilesForUri(Uri uri) + { + if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) { extracts = new SortedList(); } - return extracts.Select(e => new ExtractFile() { EmbeddedFileIndex = e.Key, OutputPath = e.Value }); + return extracts.Select(e => new ExpectedExtractFile() { Uri = uri, EmbeddedFileIndex = e.Key, OutputPath = e.Value }); } private string HashUri(string uri) @@ -72,12 +87,5 @@ namespace WixToolset.Bind return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } } - - internal struct ExtractFile - { - public int EmbeddedFileIndex { get; set; } - - public string OutputPath { get; set; } - } } } diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs index 68bfd8d7..7de40fb8 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs @@ -1,19 +1,26 @@ // 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.Bind +namespace WixToolset.Core.Bind { + using System.Collections.Generic; using System.IO; + using System.Linq; using System.Reflection; using WixToolset.Data; + using WixToolset.Extensibility; - internal class ExtractEmbeddedFilesCommand : ICommand + public class ExtractEmbeddedFilesCommand { - public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } + public IEnumerable FilesWithEmbeddedFiles { private get; set; } public void Execute() { - foreach (var baseUri in this.FilesWithEmbeddedFiles.Uris) + var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri); + + foreach (var expectedEmbeddedFileByUri in group) { + var baseUri = expectedEmbeddedFileByUri.Key; + Stream stream = null; try { @@ -34,18 +41,20 @@ namespace WixToolset.Bind using (FileStructure fs = FileStructure.Read(stream)) { - foreach (var embeddedFile in this.FilesWithEmbeddedFiles.GetExtractFilesForUri(baseUri)) + var uniqueIndicies = new SortedSet(); + + foreach (var embeddedFile in expectedEmbeddedFileByUri) { - fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath); + if (uniqueIndicies.Add(embeddedFile.EmbeddedFileIndex)) + { + fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath); + } } } } finally { - if (null != stream) - { - stream.Close(); - } + stream?.Close(); } } } diff --git a/src/WixToolset.Core/Bind/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs new file mode 100644 index 00000000..aaa6b7d3 --- /dev/null +++ b/src/WixToolset.Core/Bind/FileFacade.cs @@ -0,0 +1,44 @@ +// 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.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Rows; + + public class FileFacade + { + public FileFacade(FileRow file, WixFileRow wixFile, WixDeltaPatchFileRow deltaPatchFile) + { + this.File = file; + this.WixFile = wixFile; + this.DeltaPatchFile = deltaPatchFile; + } + + public FileFacade(bool fromModule, FileRow file, WixFileRow wixFile) + { + this.FromModule = fromModule; + this.File = file; + this.WixFile = wixFile; + } + + public bool FromModule { get; private set; } + + public FileRow File { get; private set; } + + public WixFileRow WixFile { get; private set; } + + public WixDeltaPatchFileRow DeltaPatchFile { get; private set; } + + /// + /// 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 Row Hash { get; set; } + } +} diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs new file mode 100644 index 00000000..8d624e6f --- /dev/null +++ b/src/WixToolset.Core/Bind/FileResolver.cs @@ -0,0 +1,231 @@ +// 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 System.Runtime.InteropServices; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + + 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.BinderExtensions = 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 BinderExtensions { get; } + + private IEnumerable LibrarianExtensions { get; } + + /// + /// Copies a file. + /// + /// The file to copy. + /// The destination file. + /// true if the destination file can be overwritten; otherwise, false. + public bool CopyFile(string source, string destination, bool overwrite) + { + foreach (var extension in this.BinderExtensions) + { + if (extension.CopyFile(source, destination, overwrite)) + { + return true; + } + } + + if (overwrite && File.Exists(destination)) + { + File.Delete(destination); + } + + if (!CreateHardLink(destination, source, IntPtr.Zero)) + { +#if DEBUG + int er = Marshal.GetLastWin32Error(); +#endif + + File.Copy(source, destination, overwrite); + } + + return true; + } + + /// + /// Moves a file. + /// + /// The file to move. + /// The destination file. + public bool MoveFile(string source, string destination, bool overwrite) + { + foreach (var extension in this.BinderExtensions) + { + if (extension.MoveFile(source, destination, overwrite)) + { + return true; + } + } + + if (overwrite && File.Exists(destination)) + { + File.Delete(destination); + } + + var directory = Path.GetDirectoryName(destination); + if (!String.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + + File.Move(source, destination); + + return true; + } + + public string Resolve(SourceLineNumber sourceLineNumbers, string table, string path) + { + foreach (var extension in this.LibrarianExtensions) + { + var resolved = extension.Resolve(sourceLineNumbers, table, path); + + if (null != resolved) + { + return resolved; + } + } + + return this.ResolveUsingBindPaths(path, table, sourceLineNumbers, BindStage.Normal); + } + + /// + /// 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 + /// Should return a valid path for the stream to be imported. + public string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) + { + foreach (var extension in this.BinderExtensions) + { + var resolved = extension.ResolveFile(source, type, sourceLineNumbers, bindStage); + + if (null != resolved) + { + return resolved; + } + } + + return this.ResolveUsingBindPaths(source, type, sourceLineNumbers, bindStage); + } + + private string ResolveUsingBindPaths(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) + { + string resolved = null; + + // If the file exists, we're good to go. + 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. + { + string bindName = String.Empty; + var path = source; + string pathWithoutSourceDir = null; + + if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) + { + int 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(pathWithoutSourceDir)) + { + var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); + + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + + if (String.IsNullOrEmpty(resolved)) + { + var filePath = Path.Combine(bindPath.Path, path); + + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + } + } + + if (null == resolved) + { + throw new WixFileNotFoundException(sourceLineNumbers, source, type); + } + + // Didn't find the file. + return resolved; + } + + private static bool CheckFileExists(string path) + { + try + { + return File.Exists(path); + } + catch (ArgumentException) + { + throw new WixException(WixErrors.IllegalCharactersInPath(path)); + } + } + + [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); + } +} diff --git a/src/WixToolset.Core/Bind/FileTransfer.cs b/src/WixToolset.Core/Bind/FileTransfer.cs deleted file mode 100644 index 64bbc5f1..00000000 --- a/src/WixToolset.Core/Bind/FileTransfer.cs +++ /dev/null @@ -1,113 +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.Bind -{ - using System; - using System.IO; - using WixToolset; - using WixToolset.Data; - - /// - /// Structure used for all file transfer information. - /// - internal class FileTransfer - { - /// Source path to file. - public string Source { get; set; } - - /// Destination path for file. - public string Destination { get; set; } - - /// Flag if file should be moved (optimal). - public bool Move { get; set; } - - /// Optional source line numbers where this file transfer orginated. - public SourceLineNumber SourceLineNumbers { get; set; } - - /// Optional type of file this transfer is moving or copying. - public string Type { get; set; } - - /// Indicates whether the file transer was a built by this build or copied from other some build. - internal bool Built { get; set; } - - /// Set during layout of media when the file transfer when the source and target resolve to the same path. - internal bool Redundant { get; set; } - - /// - /// Prefer the TryCreate() method to create FileTransfer objects. - /// - /// Source path to file. - /// Destination path for file. - /// File if file should be moved (optimal). - /// Optional type of file this transfer is transferring. - /// Optional source line numbers wher this transfer originated. - public FileTransfer(string source, string destination, bool move, string type = null, SourceLineNumber sourceLineNumbers = null) - { - this.Source = source; - this.Destination = destination; - this.Move = move; - - this.Type = type; - this.SourceLineNumbers = sourceLineNumbers; - } - - /// - /// Creates a file transfer if the source and destination are different. - /// - /// Source path to file. - /// Destination path for file. - /// File if file should be moved (optimal). - /// Optional type of file this transfer is transferring. - /// Optional source line numbers wher this transfer originated. - /// true if the source and destination are the different, false if no file transfer is created. - public static bool TryCreate(string source, string destination, bool move, string type, SourceLineNumber sourceLineNumbers, out FileTransfer transfer) - { - string sourceFullPath = GetValidatedFullPath(sourceLineNumbers, source); - - string fileLayoutFullPath = GetValidatedFullPath(sourceLineNumbers, destination); - - // if the current source path (where we know that the file already exists) and the resolved - // path as dictated by the Directory table are not the same, then propagate the file. The - // image that we create may have already been done by some other process other than the linker, so - // there is no reason to copy the files to the resolved source if they are already there. - if (String.Equals(sourceFullPath, fileLayoutFullPath, StringComparison.OrdinalIgnoreCase)) - { - transfer = null; - return false; - } - - transfer = new FileTransfer(source, destination, move, type, sourceLineNumbers); - return true; - } - - private static string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) - { - string result; - - try - { - result = Path.GetFullPath(path); - - string filename = Path.GetFileName(result); - - foreach (string reservedName in Common.ReservedFileNames) - { - if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase)) - { - throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path)); - } - } - } - catch (System.ArgumentException) - { - throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path)); - } - catch (System.IO.PathTooLongException) - { - throw new WixException(WixErrors.PathTooLong(sourceLineNumbers, path)); - } - - return result; - } - } -} diff --git a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs deleted file mode 100644 index fdf1ab32..00000000 --- a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs +++ /dev/null @@ -1,335 +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.Bind -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Text; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Msi; - using WixToolset.Core.Native; - - internal class GenerateDatabaseCommand : ICommand - { - public int Codepage { private get; set; } - - public IEnumerable Extensions { private get; set; } - - public IEnumerable FileManagers { private get; set; } - - /// - /// Whether to keep columns added in a transform. - /// - public bool KeepAddedColumns { private get; set; } - - public Output Output { private get; set; } - - public string OutputPath { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public string TempFilesLocation { private get; set; } - - /// - /// Whether to use a subdirectory based on the file name for intermediate files. - /// - public bool SuppressAddingValidationRows { private get; set; } - - public bool UseSubDirectory { private get; set; } - - public void Execute() - { - // Add the _Validation rows. - if (!this.SuppressAddingValidationRows) - { - Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); - - foreach (Table table in this.Output.Tables) - { - if (!table.Definition.Unreal) - { - // Add the validation rows for this table. - table.Definition.AddValidationRows(validationTable); - } - } - } - - // Set the base directory. - string baseDirectory = this.TempFilesLocation; - - if (this.UseSubDirectory) - { - string filename = Path.GetFileNameWithoutExtension(this.OutputPath); - baseDirectory = Path.Combine(baseDirectory, filename); - - // make sure the directory exists - Directory.CreateDirectory(baseDirectory); - } - - try - { - OpenDatabase type = OpenDatabase.CreateDirect; - - // set special flag for patch files - if (OutputType.Patch == this.Output.Type) - { - type |= OpenDatabase.OpenPatchFile; - } - -#if DEBUG - Console.WriteLine("Opening database at: {0}", this.OutputPath); -#endif - - using (Database db = new Database(this.OutputPath, type)) - { - // Localize the codepage if a value was specified directly. - if (-1 != this.Codepage) - { - this.Output.Codepage = this.Codepage; - } - - // if we're not using the default codepage, import a new one into our - // database before we add any tables (or the tables would be added - // with the wrong codepage). - if (0 != this.Output.Codepage) - { - this.SetDatabaseCodepage(db, this.Output.Codepage); - } - - foreach (Table table in this.Output.Tables) - { - Table importTable = table; - bool hasBinaryColumn = false; - - // Skip all unreal tables other than _Streams. - if (table.Definition.Unreal && "_Streams" != table.Name) - { - continue; - } - - // Do not put the _Validation table in patches, it is not needed. - if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name) - { - continue; - } - - // The only way to import binary data is to copy it to a local subdirectory first. - // To avoid this extra copying and perf hit, import an empty table with the same - // definition and later import the binary data from source using records. - foreach (ColumnDefinition columnDefinition in table.Definition.Columns) - { - if (ColumnType.Object == columnDefinition.Type) - { - importTable = new Table(table.Section, table.Definition); - hasBinaryColumn = true; - break; - } - } - - // Create the table via IDT import. - if ("_Streams" != importTable.Name) - { - try - { - db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); - } - catch (WixInvalidIdtException) - { - // If ValidateRows finds anything it doesn't like, it throws - importTable.ValidateRows(); - - // Otherwise we rethrow the InvalidIdt - throw; - } - } - - // insert the rows via SQL query if this table contains object fields - if (hasBinaryColumn) - { - StringBuilder query = new StringBuilder("SELECT "); - - // Build the query for the view. - bool firstColumn = true; - foreach (ColumnDefinition columnDefinition in table.Definition.Columns) - { - if (!firstColumn) - { - query.Append(","); - } - - query.AppendFormat(" `{0}`", columnDefinition.Name); - firstColumn = false; - } - query.AppendFormat(" FROM `{0}`", table.Name); - - using (View tableView = db.OpenExecuteView(query.ToString())) - { - // Import each row containing a stream - foreach (Row row in table.Rows) - { - using (Record record = new Record(table.Definition.Columns.Count)) - { - StringBuilder streamName = new StringBuilder(); - bool needStream = false; - - // the _Streams table doesn't prepend the table name (or a period) - if ("_Streams" != table.Name) - { - streamName.Append(table.Name); - } - - for (int i = 0; i < table.Definition.Columns.Count; i++) - { - ColumnDefinition columnDefinition = table.Definition.Columns[i]; - - switch (columnDefinition.Type) - { - case ColumnType.Localized: - case ColumnType.Preserved: - case ColumnType.String: - if (columnDefinition.PrimaryKey) - { - if (0 < streamName.Length) - { - streamName.Append("."); - } - streamName.Append((string)row[i]); - } - - record.SetString(i + 1, (string)row[i]); - break; - case ColumnType.Number: - record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); - break; - case ColumnType.Object: - if (null != row[i]) - { - needStream = true; - try - { - record.SetStream(i + 1, (string)row[i]); - } - catch (Win32Exception e) - { - if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME - { - throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); - } - else - { - throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); - } - } - } - break; - } - } - - // stream names are created by concatenating the name of the table with the values - // of the primary key (delimited by periods) - // check for a stream name that is more than 62 characters long (the maximum allowed length) - if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) - { - Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); - } - else // add the row to the database - { - tableView.Modify(ModifyView.Assign, record); - } - } - } - } - - // Remove rows from the _Streams table for wixpdbs. - if ("_Streams" == table.Name) - { - table.Rows.Clear(); - } - } - } - - // Insert substorages (usually transforms inside a patch or instance transforms in a package). - if (0 < this.Output.SubStorages.Count) - { - using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) - { - foreach (SubStorage subStorage in this.Output.SubStorages) - { - string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); - - // Bind the transform. - this.BindTransform(subStorage.Data, transformFile); - - if (Messaging.Instance.EncounteredError) - { - continue; - } - - // add the storage - using (Record record = new Record(2)) - { - record.SetString(1, subStorage.Name); - record.SetStream(2, transformFile); - storagesView.Modify(ModifyView.Assign, record); - } - } - } - } - - // We're good, commit the changes to the new database. - db.Commit(); - } - } - catch (IOException) - { - // TODO: this error message doesn't seem specific enough - throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath); - } - } - - private void BindTransform(Output transform, string outputPath) - { - BindTransformCommand command = new BindTransformCommand(); - command.Extensions = this.Extensions; - command.FileManagers = this.FileManagers; - command.TempFilesLocation = this.TempFilesLocation; - command.Transform = transform; - command.OutputPath = outputPath; - command.TableDefinitions = this.TableDefinitions; - command.Execute(); - } - - /// - /// Sets the codepage of a database. - /// - /// Database to set codepage into. - /// Output with the codepage for the database. - private void SetDatabaseCodepage(Database db, int codepage) - { - // write out the _ForceCodepage IDT file - string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"); - using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) - { - idtFile.WriteLine(); // dummy column name record - idtFile.WriteLine(); // dummy column definition record - idtFile.Write(codepage); - idtFile.WriteLine("\t_ForceCodepage"); - } - - // try to import the table into the MSI - try - { - db.Import(idtPath); - } - catch (WixInvalidIdtException) - { - // the IDT should be valid, so an invalid code page was given - throw new WixException(WixErrors.IllegalCodepage(codepage)); - } - } - } -} diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs index 4ffe9e82..15365c2a 100644 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs @@ -1,23 +1,22 @@ // 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.Bind +namespace WixToolset.Core.Bind { using System; using System.Collections.Generic; using System.Globalization; - using System.Linq; - using System.Text; using WixToolset.Data; + using WixToolset.Extensibility; /// /// Resolves the fields which had variables that needed to be resolved after the file information /// was loaded. /// - internal class ResolveDelayedFieldsCommand : ICommand + public class ResolveDelayedFieldsCommand : ICommand { public OutputType OutputType { private get; set;} - public IEnumerable DelayedFields { private get; set;} + public IEnumerable DelayedFields { private get; set;} public IDictionary VariableCache { private get; set; } @@ -29,9 +28,9 @@ namespace WixToolset.Bind /// The modularization guid (used in case of a merge module). public void Execute() { - List deferredFields = new List(); + var deferredFields = new List(); - foreach (DelayedField delayedField in this.DelayedFields) + foreach (IDelayedField delayedField in this.DelayedFields) { try { @@ -43,7 +42,7 @@ namespace WixToolset.Bind string value = WixVariableResolver.ResolveDelayedVariables(propertyRow.SourceLineNumbers, (string)delayedField.Field.Data, this.VariableCache); // update the variable cache with the new value - string key = String.Concat("property.", BindDatabaseCommand.Demodularize(this.OutputType, this.ModularizationGuid, (string)propertyRow[0])); + string key = String.Concat("property.", Common.Demodularize(this.OutputType, this.ModularizationGuid, (string)propertyRow[0])); this.VariableCache[key] = value; // update the field data diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs index 4caec9b4..f4f4f9e8 100644 --- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs @@ -1,29 +1,32 @@ // 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.Bind +namespace WixToolset.Core.Bind { using System.Collections.Generic; using WixToolset.Data; + using WixToolset.Data.Bind; using WixToolset.Extensibility; /// /// Resolve source fields in the tables included in the output /// - internal class ResolveFieldsCommand : ICommand + internal class ResolveFieldsCommand { - public TableIndexedCollection Tables { private get; set; } + public bool BuildingPatch { private get; set; } - public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } + public IBindVariableResolver BindVariableResolver { private get; set; } - public BinderFileManagerCore FileManagerCore { private get; set; } + public IEnumerable BindPaths { private get; set; } - public IEnumerable FileManagers { private get; set; } + public IEnumerable Extensions { private get; set; } - public bool SupportDelayedResolution { private get; set; } + public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } + + public string IntermediateFolder { private get; set; } - public string TempFilesLocation { private get; set; } + public TableIndexedCollection Tables { private get; set; } - public WixVariableResolver WixVariableResolver { private get; set; } + public bool SupportDelayedResolution { private get; set; } public IEnumerable DelayedFields { get; private set; } @@ -31,6 +34,8 @@ namespace WixToolset.Bind { List delayedFields = this.SupportDelayedResolution ? new List() : null; + var fileResolver = new FileResolver(this.BindPaths, this.Extensions); + foreach (Table table in this.Tables) { foreach (Row row in table.Rows) @@ -46,7 +51,7 @@ namespace WixToolset.Bind // resolve localization and wix variables if (field.Data is string) { - field.Data = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, field.AsString(), false, ref isDefault, ref delayedResolve); + field.Data = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, field.AsString(), false, out isDefault, out delayedResolve); if (delayedResolve) { delayedFields.Add(new DelayedField(row, field)); @@ -74,7 +79,7 @@ namespace WixToolset.Bind // File is embedded and path to it was not modified above. if (objectField.EmbeddedFileIndex.HasValue && isDefault) { - string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.TempFilesLocation); + string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.IntermediateFolder); // Set the path to the embedded file once where it will be extracted. objectField.Data = extractPath; @@ -83,7 +88,7 @@ namespace WixToolset.Bind { try { - if (OutputType.Patch != this.FileManagerCore.Output.Type) // Normal binding for non-Patch scenario such as link (light.exe) + if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) { // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file if (null == objectField.UnresolvedData) @@ -92,12 +97,12 @@ namespace WixToolset.Bind } // resolve the path to the file - objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); + objectField.Data = fileResolver.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); } - else if (!(this.FileManagerCore.RebaseTarget || this.FileManagerCore.RebaseUpdated)) // Normal binding for Patch Scenario (normal patch, no re-basing logic) + else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) { // resolve the path to the file - objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); + objectField.Data = fileResolver.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); } else // Re-base binding path scenario caused by pyro.exe -bt -bu { @@ -106,7 +111,7 @@ namespace WixToolset.Bind // if -bu is used in pyro command, this condition holds true and the tool // will use pre-resolved source for new wixpdb file - if (this.FileManagerCore.RebaseUpdated) + 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 @@ -117,7 +122,7 @@ namespace WixToolset.Bind } } - objectField.Data = this.ResolveFile(filePathToResolve, table.Name, row.SourceLineNumbers, BindStage.Updated); + objectField.Data = fileResolver.ResolveFile(filePathToResolve, table.Name, row.SourceLineNumbers, BindStage.Updated); } } catch (WixFileNotFoundException) @@ -127,10 +132,10 @@ namespace WixToolset.Bind } } - isDefault = true; if (null != objectField.PreviousData) { - objectField.PreviousData = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, objectField.PreviousData, false, ref isDefault); + objectField.PreviousData = this.BindVariableResolver.ResolveVariables(row.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. { // file is compressed in a cabinet (and not modified above) @@ -142,7 +147,7 @@ namespace WixToolset.Bind objectField.PreviousBaseUri = objectField.BaseUri; } - string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.TempFilesLocation); + 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; @@ -151,14 +156,14 @@ namespace WixToolset.Bind { try { - if (!this.FileManagerCore.RebaseTarget && !this.FileManagerCore.RebaseUpdated) + if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) { // resolve the path to the file - objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal); + objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal); } else { - if (this.FileManagerCore.RebaseTarget) + if (fileResolver.RebaseTarget) { // if -bt is used, it come here // Try to use the original unresolved source from either target build or update build @@ -172,7 +177,7 @@ namespace WixToolset.Bind } // resolve the path to the file - objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Target); + objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Target); } } @@ -192,24 +197,28 @@ namespace WixToolset.Bind this.DelayedFields = delayedFields; } +#if false private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal) { string path = null; - foreach (IBinderFileManager fileManager in this.FileManagers) + foreach (var extension in this.Extensions) { - path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage); + path = extension.ResolveFile(source, type, sourceLineNumbers, bindStage); if (null != path) { break; } } - if (null == path) - { - throw new WixFileNotFoundException(sourceLineNumbers, source, type); - } + throw new NotImplementedException(); // need to do default binder stuff + + //if (null == path) + //{ + // throw new WixFileNotFoundException(sourceLineNumbers, source, type); + //} - return path; + //return path; } +#endif } } diff --git a/src/WixToolset.Core/Bind/ResolveResult.cs b/src/WixToolset.Core/Bind/ResolveResult.cs new file mode 100644 index 00000000..13f25054 --- /dev/null +++ b/src/WixToolset.Core/Bind/ResolveResult.cs @@ -0,0 +1,14 @@ +// 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.Collections.Generic; + using WixToolset.Extensibility; + + public class ResolveResult + { + public IEnumerable ExpectedEmbeddedFiles { get; set; } + + public IEnumerable DelayedFields { get; set; } + } +} \ No newline at end of file diff --git a/src/WixToolset.Core/Bind/ResolvedDirectory.cs b/src/WixToolset.Core/Bind/ResolvedDirectory.cs index 6985f95d..fca706d8 100644 --- a/src/WixToolset.Core/Bind/ResolvedDirectory.cs +++ b/src/WixToolset.Core/Bind/ResolvedDirectory.cs @@ -5,7 +5,7 @@ namespace WixToolset.Bind /// /// Structure used for resolved directory information. /// - internal struct ResolvedDirectory + public struct ResolvedDirectory { /// The directory parent. public string DirectoryParent; diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs index 719b8b20..f116569c 100644 --- a/src/WixToolset.Core/Bind/TransferFilesCommand.cs +++ b/src/WixToolset.Core/Bind/TransferFilesCommand.cs @@ -1,29 +1,37 @@ // 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.Bind +namespace WixToolset.Core.Bind { using System; using System.Collections.Generic; using System.IO; using System.Security.AccessControl; using WixToolset.Data; + using WixToolset.Data.Bind; using WixToolset.Extensibility; - internal class TransferFilesCommand : ICommand + internal class TransferFilesCommand { - public IEnumerable FileManagers { private get; set; } + public TransferFilesCommand(IEnumerable bindPaths, IEnumerable extensions, IEnumerable fileTransfers, bool suppressAclReset) + { + this.FileResolver = new FileResolver(bindPaths, extensions); + this.FileTransfers = fileTransfers; + this.SuppressAclReset = suppressAclReset; + } + + private FileResolver FileResolver { get; } - public IEnumerable FileTransfers { private get; set; } + private IEnumerable FileTransfers { get; } - public bool SuppressAclReset { private get; set; } + private bool SuppressAclReset { get; } public void Execute() { List destinationFiles = new List(); - foreach (FileTransfer fileTransfer in this.FileTransfers) + foreach (var fileTransfer in this.FileTransfers) { - string fileSource = this.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal); + string fileSource = this.FileResolver.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal); // If the source and destination are identical, then there's nothing to do here if (0 == String.Compare(fileSource, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) @@ -165,44 +173,17 @@ namespace WixToolset.Bind } } - private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) + private void TransferFile(bool move, string source, string destination) { - string path = null; - foreach (IBinderFileManager fileManager in this.FileManagers) - { - path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage); - if (null != path) - { - break; - } - } + bool complete = false; - if (null == path) + if (move) { - throw new WixFileNotFoundException(sourceLineNumbers, source, type); + complete = this.FileResolver.MoveFile(source, destination, true); } - - return path; - } - - private void TransferFile(bool move, string source, string destination) - { - bool complete = false; - foreach (IBinderFileManager fileManager in this.FileManagers) + else { - if (move) - { - complete = fileManager.MoveFile(source, destination, true); - } - else - { - complete = fileManager.CopyFile(source, destination, true); - } - - if (complete) - { - break; - } + complete = this.FileResolver.CopyFile(source, destination, true); } if (!complete) diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs new file mode 100644 index 00000000..499f3245 --- /dev/null +++ b/src/WixToolset.Core/BindContext.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + + public class BindContext : IBindContext + { + public Messaging Messaging { get; set; } + + public IEnumerable BindPaths { get; set; } + + public int CabbingThreadCount { get; set; } + + public string CabCachePath { get; set; } + + public int Codepage { get; set; } + + public CompressionLevel DefaultCompressionLevel { get; set; } + + public IEnumerable DelayedFields { get; set; } + + public IEnumerable ExpectedEmbeddedFiles { get; set; } + + public IExtensionManager ExtensionManager { get; set; } + + public IEnumerable Extensions { get; set; } + + public IEnumerable Ices { get; set; } + + public string IntermediateFolder { get; set; } + + public Output IntermediateRepresentation { get; set; } + + public string OutputPath { get; set; } + + public string OutputPdbPath { get; set; } + + public bool SuppressAclReset { get; set; } + + public IEnumerable SuppressIces { get; set; } + + public bool SuppressValidation { get; set; } + + public IBindVariableResolver WixVariableResolver { get; set; } + + public string ContentsFile { get; set; } + + public string OutputsFile { get; set; } + + public string BuiltOutputsFile { get; set; } + + public string WixprojectFile { get; set; } + } +} diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs index 18ad2d62..43c15634 100644 --- a/src/WixToolset.Core/Binder.cs +++ b/src/WixToolset.Core/Binder.cs @@ -1,6 +1,6 @@ // 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 +namespace WixToolset.Core { using System; using System.Collections; @@ -11,105 +11,72 @@ namespace WixToolset using System.Linq; using System.Reflection; using WixToolset.Bind; + using WixToolset.Core.Bind; using WixToolset.Data; + using WixToolset.Data.Bind; using WixToolset.Data.Rows; using WixToolset.Extensibility; - using WixToolset.Msi; - - // TODO: (4.0) Refactor so that these don't need to be copied. - // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs - [Flags] - internal enum WixFileSearchAttributes - { - Default = 0x001, - MinVersionInclusive = 0x002, - MaxVersionInclusive = 0x004, - MinSizeInclusive = 0x008, - MaxSizeInclusive = 0x010, - MinDateInclusive = 0x020, - MaxDateInclusive = 0x040, - WantVersion = 0x080, - WantExists = 0x100, - IsDirectory = 0x200, - } - - [Flags] - internal enum WixRegistrySearchAttributes - { - Raw = 0x01, - Compatible = 0x02, - ExpandEnvironmentVariables = 0x04, - WantValue = 0x08, - WantExists = 0x10, - Win64 = 0x20, - } - - internal enum WixComponentSearchAttributes - { - KeyPath = 0x1, - State = 0x2, - WantDirectory = 0x4, - } - - [Flags] - internal enum WixProductSearchAttributes - { - Version = 0x1, - Language = 0x2, - State = 0x4, - Assignment = 0x8, - UpgradeCode = 0x10, - } /// /// Binder of the WiX toolset. /// public sealed class Binder { - private BinderCore core; - private BinderFileManagerCore fileManagerCore; - private List extensions; - private List fileManagers; - private List inspectorExtensions; + //private BinderCore core; + //private List extensions; + //private List fileManagers; public Binder() { - this.DefaultCompressionLevel = CompressionLevel.High; + //this.DefaultCompressionLevel = CompressionLevel.High; - this.BindPaths = new List(); - this.TargetBindPaths = new List(); - this.UpdatedBindPaths = new List(); + //this.BindPaths = new List(); + //this.TargetBindPaths = new List(); + //this.UpdatedBindPaths = new List(); - this.extensions = new List(); - this.fileManagers = new List(); - this.inspectorExtensions = new List(); + //this.extensions = new List(); + //this.fileManagers = new List(); + //this.inspectorExtensions = new List(); - this.Ices = new List(); - this.SuppressIces = new List(); + //this.Ices = new List(); + //this.SuppressIces = new List(); } - public string ContentsFile { private get; set; } + public Binder(BindContext context) + { + this.Context = context; + + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + } + + private BindContext Context { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + //public IEnumerable BackendFactories { get; set; } - public string OutputsFile { private get; set; } + //public string ContentsFile { private get; set; } - public string BuiltOutputsFile { private get; set; } + //public string OutputsFile { private get; set; } - public string WixprojectFile { private get; set; } + //public string BuiltOutputsFile { private get; set; } + + //public string WixprojectFile { private get; set; } /// /// Gets the list of bindpaths. /// - public List BindPaths { get; private set; } + //public List BindPaths { get; private set; } /// /// Gets the list of target bindpaths. /// - public List TargetBindPaths { get; private set; } + //public List TargetBindPaths { get; private set; } /// /// Gets the list of updated bindpaths. /// - public List UpdatedBindPaths { get; private set; } + //public List UpdatedBindPaths { get; private set; } /// /// Gets or sets the option to enable building binary delta patches. @@ -132,17 +99,17 @@ namespace WixToolset /// Gets or sets the default compression level to use for cabinets /// that don't have their compression level explicitly set. /// - public CompressionLevel DefaultCompressionLevel { get; set; } + //public CompressionLevel DefaultCompressionLevel { get; set; } /// /// Gets and sets the location to save the WixPdb. /// /// The location in which to save the WixPdb. Null if the the WixPdb should not be output. - public string PdbFile { get; set; } + //public string PdbFile { get; set; } - public List Ices { get; private set; } + //public List Ices { get; private set; } - public List SuppressIces { get; private set; } + //public List SuppressIces { get; private set; } /// /// Gets and sets the option to suppress resetting ACLs by the binder. @@ -185,24 +152,164 @@ namespace WixToolset /// Gets or sets the Wix variable resolver. /// /// The Wix variable resolver. - public WixVariableResolver WixVariableResolver { get; set; } + internal WixVariableResolver WixVariableResolver { get; set; } /// /// Add a binder extension. /// /// New extension. - public void AddExtension(IBinderExtension extension) - { - this.extensions.Add(extension); - } + //public void AddExtension(IBinderExtension extension) + //{ + // this.extensions.Add(extension); + //} /// /// Add a file manager extension. /// /// New file manager. - public void AddExtension(IBinderFileManager extension) + //public void AddExtension(IBinderFileManager extension) + //{ + // this.fileManagers.Add(extension); + //} + + public bool Bind() + { + //if (!String.IsNullOrEmpty(this.Context.FileManagerCore.CabCachePath)) + //{ + // Directory.CreateDirectory(this.Context.FileManagerCore.CabCachePath); + //} + + //this.core = new BinderCore(); + //this.core.FileManagerCore = this.Context.FileManagerCore; + + this.WriteBuildInfoTable(this.Context.IntermediateRepresentation, this.Context.OutputPath); + + // Prebind. + // + this.Context.Extensions = this.Context.ExtensionManager.Create(); + + foreach (IBinderExtension extension in this.Context.Extensions) + { + extension.PreBind(this.Context); + } + + // Resolve. + // + var resolveResult = this.Resolve(); + + this.Context.DelayedFields = resolveResult.DelayedFields; + + this.Context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; + + // Backend. + // + var bindResult = this.BackendBind(); + + if (bindResult != null) + { + // Postbind. + // + foreach (IBinderExtension extension in this.Context.Extensions) + { + extension.PostBind(bindResult); + } + + // Layout. + // + this.Layout(bindResult); + } + + return Messaging.Instance.EncounteredError; + } + + private ResolveResult Resolve() + { + var buildingPatch = (this.Context.IntermediateRepresentation.Type == OutputType.Patch); + + var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); + + IEnumerable delayedFields; + { + var command = new ResolveFieldsCommand(); + command.BuildingPatch = buildingPatch; + command.BindVariableResolver = this.Context.WixVariableResolver; + command.BindPaths = this.Context.BindPaths; + command.Extensions = this.Context.Extensions; + command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + command.IntermediateFolder = this.Context.IntermediateFolder; + command.Tables = this.Context.IntermediateRepresentation.Tables; + command.SupportDelayedResolution = true; + command.Execute(); + + delayedFields = command.DelayedFields; + } + + if (this.Context.IntermediateRepresentation.SubStorages != null) + { + foreach (SubStorage transform in this.Context.IntermediateRepresentation.SubStorages) + { + var command = new ResolveFieldsCommand(); + command.BuildingPatch = buildingPatch; + command.BindVariableResolver = this.Context.WixVariableResolver; + command.BindPaths = this.Context.BindPaths; + command.Extensions = this.Context.Extensions; + command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + command.IntermediateFolder = this.Context.IntermediateFolder; + command.Tables = transform.Data.Tables; + command.SupportDelayedResolution = false; + command.Execute(); + } + } + + var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); + + return new ResolveResult + { + ExpectedEmbeddedFiles = expectedEmbeddedFiles, + DelayedFields = delayedFields, + }; + } + + private BindResult BackendBind() + { + var backendFactories = this.Context.ExtensionManager.Create(); + + foreach (var factory in backendFactories) + { + if (factory.TryCreateBackend(this.Context.IntermediateRepresentation.Type.ToString(), this.Context.OutputPath, null, out var backend)) + { + var result = backend.Bind(this.Context); + return result; + } + } + + // TODO: messaging that a backend could not be found to bind the output type? + + return null; + } + private void Layout(BindResult result) { - this.fileManagers.Add(extension); + try + { + this.LayoutMedia(result.FileTransfers); + } + finally + { + if (!String.IsNullOrEmpty(this.Context.ContentsFile) && result.ContentFilePaths != null) + { + this.CreateContentsFile(this.Context.ContentsFile, result.ContentFilePaths); + } + + if (!String.IsNullOrEmpty(this.Context.OutputsFile) && result.FileTransfers != null) + { + this.CreateOutputsFile(this.Context.OutputsFile, result.FileTransfers, this.Context.OutputPdbPath); + } + + if (!String.IsNullOrEmpty(this.Context.BuiltOutputsFile) && result.FileTransfers != null) + { + this.CreateBuiltOutputsFile(this.Context.BuiltOutputsFile, result.FileTransfers, this.Context.OutputPdbPath); + } + } } /// @@ -212,6 +319,7 @@ namespace WixToolset /// The Windows Installer file to create. /// The Binder.DeleteTempFiles method should be called after calling this method. /// true if binding completed successfully; false otherwise +#if false public bool Bind(Output output, string file) { // Ensure the cabinet cache path exists if we are going to use it. @@ -220,20 +328,20 @@ namespace WixToolset Directory.CreateDirectory(this.CabCachePath); } - this.fileManagerCore = new BinderFileManagerCore(); - this.fileManagerCore.CabCachePath = this.CabCachePath; - this.fileManagerCore.Output = output; - this.fileManagerCore.TempFilesLocation = this.TempFilesLocation; - this.fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal); - this.fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target); - this.fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated); - foreach (IBinderFileManager fileManager in this.fileManagers) - { - fileManager.Core = this.fileManagerCore; - } + //var fileManagerCore = new BinderFileManagerCore(); + //fileManagerCore.CabCachePath = this.CabCachePath; + //fileManagerCore.Output = output; + //fileManagerCore.TempFilesLocation = this.TempFilesLocation; + //fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal); + //fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target); + //fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated); + //foreach (IBinderFileManager fileManager in this.fileManagers) + //{ + // fileManager.Core = fileManagerCore; + //} this.core = new BinderCore(); - this.core.FileManagerCore = this.fileManagerCore; + this.core.FileManagerCore = fileManagerCore; this.WriteBuildInfoTable(output, file); @@ -246,54 +354,69 @@ namespace WixToolset } // Gather all the wix variables. - Table wixVariableTable = output.Tables["WixVariable"]; - if (null != wixVariableTable) + //Table wixVariableTable = output.Tables["WixVariable"]; + //if (null != wixVariableTable) + //{ + // foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) + // { + // this.WixVariableResolver.AddVariable(wixVariableRow); + // } + //} + + //BindContext context = new BindContext(); + //context.CabbingThreadCount = this.CabbingThreadCount; + //context.DefaultCompressionLevel = this.DefaultCompressionLevel; + //context.Extensions = this.extensions; + //context.FileManagerCore = fileManagerCore; + //context.FileManagers = this.fileManagers; + //context.Ices = this.Ices; + //context.IntermediateFolder = this.TempFilesLocation; + //context.IntermediateRepresentation = output; + //context.Localizer = this.Localizer; + //context.OutputPath = file; + //context.OutputPdbPath = this.PdbFile; + //context.SuppressIces = this.SuppressIces; + //context.SuppressValidation = this.SuppressValidation; + //context.WixVariableResolver = this.WixVariableResolver; + + BindResult result = null; + + foreach (var factory in this.BackendFactories) { - foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) + if (factory.TryCreateBackend(output.Type.ToString(), file, null, out var backend)) { - this.WixVariableResolver.AddVariable(wixVariableRow); + result = backend.Bind(context); + break; } } - IEnumerable fileTransfers = null; - IEnumerable contentPaths = null; - - switch (output.Type) + if (result == null) { - case OutputType.Bundle: - this.BindBundle(output, file, out fileTransfers, out contentPaths); - break; - - case OutputType.Transform: - this.BindTransform(output, file); - break; + // TODO: messaging that a backend could not be found to bind the output type? - default: - this.BindDatabase(output, file, out fileTransfers, out contentPaths); - break; + return false; } - // Layout media try { - this.LayoutMedia(fileTransfers); + this.LayoutMedia(result.FileTransfers); } finally { - if (!String.IsNullOrEmpty(this.ContentsFile) && contentPaths != null) + if (!String.IsNullOrEmpty(this.ContentsFile) && result.ContentFilePaths != null) { - this.CreateContentsFile(this.ContentsFile, contentPaths); + this.CreateContentsFile(this.ContentsFile, result.ContentFilePaths); } - if (!String.IsNullOrEmpty(this.OutputsFile) && fileTransfers != null) + if (!String.IsNullOrEmpty(this.OutputsFile) && result.FileTransfers != null) { - this.CreateOutputsFile(this.OutputsFile, fileTransfers, this.PdbFile); + this.CreateOutputsFile(this.OutputsFile, result.FileTransfers, this.PdbFile); } - if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && fileTransfers != null) + if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && result.FileTransfers != null) { - this.CreateBuiltOutputsFile(this.BuiltOutputsFile, fileTransfers, this.PdbFile); + this.CreateBuiltOutputsFile(this.BuiltOutputsFile, result.FileTransfers, this.PdbFile); } } @@ -301,6 +424,7 @@ namespace WixToolset return Messaging.Instance.EncounteredError; } +#endif /// /// Does any housekeeping after Bind. @@ -312,12 +436,12 @@ namespace WixToolset { if (!this.DeleteTempFiles()) { - this.core.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation)); + this.Context.Messaging.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation)); } } else { - this.core.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation)); + this.Context.Messaging.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation)); } } @@ -327,7 +451,7 @@ namespace WixToolset /// True if all files were deleted, false otherwise. private bool DeleteTempFiles() { - bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.core); + bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.Context.Messaging); return deleted; } @@ -338,7 +462,7 @@ namespace WixToolset /// The output file if OutputFile not set. private void WriteBuildInfoTable(Output output, string outputFile) { - Table buildInfoTable = output.EnsureTable(this.core.TableDefinitions["WixBuildInfo"]); + Table buildInfoTable = output.EnsureTable(this.TableDefinitions["WixBuildInfo"]); Row buildInfoRow = buildInfoTable.CreateRow(null); Assembly executingAssembly = Assembly.GetExecutingAssembly(); @@ -346,17 +470,18 @@ namespace WixToolset buildInfoRow[0] = fileVersion.FileVersion; buildInfoRow[1] = outputFile; - if (!String.IsNullOrEmpty(this.WixprojectFile)) + if (!String.IsNullOrEmpty(this.Context.WixprojectFile)) { - buildInfoRow[2] = this.WixprojectFile; + buildInfoRow[2] = this.Context.WixprojectFile; } - if (!String.IsNullOrEmpty(this.PdbFile)) + if (!String.IsNullOrEmpty(this.Context.OutputPdbPath)) { - buildInfoRow[3] = this.PdbFile; + buildInfoRow[3] = this.Context.OutputPdbPath; } } +#if DELETE_THIS_CODE /// /// Binds a bundle. /// @@ -454,6 +579,7 @@ namespace WixToolset command.OutputPath = outputPath; command.Execute(); } +#endif /// /// Final step in binding that transfers (moves/copies) all files generated into the appropriate @@ -464,12 +590,9 @@ namespace WixToolset { if (null != transfers && transfers.Any()) { - this.core.OnMessage(WixVerboses.LayingOutMedia()); + this.Context.Messaging.OnMessage(WixVerboses.LayingOutMedia()); - TransferFilesCommand command = new TransferFilesCommand(); - command.FileManagers = this.fileManagers; - command.FileTransfers = transfers; - command.SuppressAclReset = this.SuppressAclReset; + var command = new TransferFilesCommand(this.Context.BindPaths, this.Context.Extensions, transfers, this.Context.SuppressAclReset); command.Execute(); } } @@ -482,7 +605,7 @@ namespace WixToolset /// Directory identifier. /// Canonicalize the path for standard directories. /// Source path of a directory. - internal static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize) + public static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize) { if (!directories.Contains(directory)) { @@ -543,9 +666,9 @@ namespace WixToolset /// Specifies the package is compressed. /// Specifies the package uses long file names. /// Source path of file relative to package directory. - internal static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName) + public static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName) { - string fileSourcePath = Installer.GetName(fileName, true, useLongName); + string fileSourcePath = Common.GetName(fileName, true, useLongName); if (compressed) { diff --git a/src/WixToolset.Core/BinderFileManager.cs b/src/WixToolset.Core/BinderFileManager.cs index 0da54002..1527d93d 100644 --- a/src/WixToolset.Core/BinderFileManager.cs +++ b/src/WixToolset.Core/BinderFileManager.cs @@ -12,6 +12,7 @@ namespace WixToolset using WixToolset.Data.Rows; using WixToolset.Extensibility; +#if false /// /// Base class for creating a binder file manager. /// @@ -367,4 +368,5 @@ namespace WixToolset [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); } +#endif } diff --git a/src/WixToolset.Core/BinderFileManagerCore.cs b/src/WixToolset.Core/BinderFileManagerCore.cs index 6a5e1d5e..f1a78880 100644 --- a/src/WixToolset.Core/BinderFileManagerCore.cs +++ b/src/WixToolset.Core/BinderFileManagerCore.cs @@ -6,6 +6,7 @@ namespace WixToolset using System.Collections.Generic; using System.Linq; using WixToolset.Data; + using WixToolset.Data.Bind; using WixToolset.Extensibility; public class BinderFileManagerCore : IBinderFileManagerCore diff --git a/src/WixToolset.Core/CLR/Interop/CLRInterop.cs b/src/WixToolset.Core/CLR/Interop/CLRInterop.cs deleted file mode 100644 index 4157f23a..00000000 --- a/src/WixToolset.Core/CLR/Interop/CLRInterop.cs +++ /dev/null @@ -1,147 +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.Clr.Interop -{ - using System; - using System.Runtime.InteropServices; - - /// - /// Interop class for mscorwks.dll assembly name APIs. - /// - internal sealed class ClrInterop - { - private static readonly Guid referenceIdentityGuid = new Guid("6eaf5ace-7917-4f3c-b129-e046a9704766"); - - /// - /// Protect the constructor. - /// - private ClrInterop() - { - } - - /// - /// Represents a reference to the unique signature of a code object. - /// - [ComImport] - [Guid("6eaf5ace-7917-4f3c-b129-e046a9704766")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IReferenceIdentity - { - /// - /// Get an assembly attribute. - /// - /// Attribute namespace. - /// Attribute name. - /// The assembly attribute. - [return: MarshalAs(UnmanagedType.LPWStr)] - string GetAttribute( - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName); - - /// - /// Set an assembly attribute. - /// - /// Attribute namespace. - /// Attribute name. - /// Attribute value. - void SetAttribute( - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName, - [In, MarshalAs(UnmanagedType.LPWStr)] string attributeValue); - - /// - /// Get an iterator for the assembly's attributes. - /// - /// Assembly attribute enumerator. - IEnumIDENTITY_ATTRIBUTE EnumAttributes(); - - /// - /// Clone an IReferenceIdentity. - /// - /// Count of deltas. - /// The deltas. - /// Cloned IReferenceIdentity. - IReferenceIdentity Clone( - [In] IntPtr /*SIZE_T*/ countOfDeltas, - [In, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] deltas); - } - - /// - /// IEnumIDENTITY_ATTRIBUTE interface. - /// - [ComImport] - [Guid("9cdaae75-246e-4b00-a26d-b9aec137a3eb")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IEnumIDENTITY_ATTRIBUTE - { - /// - /// Gets the next attributes. - /// - /// Count of elements. - /// Array of attributes being returned. - /// The next attribute. - uint Next( - [In] uint celt, - [Out, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] attributes); - - /// - /// Copy the current attribute into a buffer. - /// - /// Number of available bytes. - /// Buffer into which attribute should be written. - /// Pointer to buffer containing the attribute. - IntPtr CurrentIntoBuffer( - [In] IntPtr /*SIZE_T*/ available, - [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data); - - /// - /// Skip past a number of elements. - /// - /// Count of elements to skip. - void Skip([In] uint celt); - - /// - /// Reset the enumeration to the beginning. - /// - void Reset(); - - /// - /// Clone this attribute enumeration. - /// - /// Clone of a IEnumIDENTITY_ATTRIBUTE. - IEnumIDENTITY_ATTRIBUTE Clone(); - } - - /// - /// Gets the guid. - /// - public static Guid ReferenceIdentityGuid - { - get { return referenceIdentityGuid; } - } - - /// - /// Gets an interface pointer to an object with the specified IID, in the assembly at the specified file path. - /// - /// A valid path to the requested assembly. - /// The IID of the interface to return. - /// The returned interface pointer. - /// The error code. - [DllImport("mscorwks.dll", CharSet = CharSet.Unicode, EntryPoint = "GetAssemblyIdentityFromFile")] - internal static extern uint GetAssemblyIdentityFromFile(System.String wszAssemblyPath, ref Guid riid, out IReferenceIdentity i); - - /// - /// Assembly attributes. Contains data about an IReferenceIdentity. - /// - [StructLayout(LayoutKind.Sequential)] - internal struct IDENTITY_ATTRIBUTE - { - [MarshalAs(UnmanagedType.LPWStr)] - public string AttributeNamespace; - [MarshalAs(UnmanagedType.LPWStr)] - public string AttributeName; - [MarshalAs(UnmanagedType.LPWStr)] - public string AttributeValue; - } - } -} diff --git a/src/WixToolset.Core/Cab/CabinetFileInfo.cs b/src/WixToolset.Core/Cab/CabinetFileInfo.cs index 849bb3bb..816f9e3e 100644 --- a/src/WixToolset.Core/Cab/CabinetFileInfo.cs +++ b/src/WixToolset.Core/Cab/CabinetFileInfo.cs @@ -1,19 +1,12 @@ // 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 +namespace WixToolset.Core.Cab { - using System; - /// /// Properties of a file in a cabinet. /// - internal sealed class CabinetFileInfo + public sealed class CabinetFileInfo { - private string fileId; - private ushort date; - private ushort time; - private int size; - /// /// Constructs CabinetFileInfo /// @@ -22,43 +15,31 @@ namespace WixToolset /// Last modified time (MS-DOS time) public CabinetFileInfo(string fileId, ushort date, ushort time, int size) { - this.fileId = fileId; - this.date = date; - this.time = time; - this.size = size; + this.FileId = fileId; + this.Date = date; + this.Time = time; + this.Size = size; } /// /// Gets the file Id of the file. /// /// file Id - public string FileId - { - get { return this.fileId; } - } + public string FileId { get; } /// /// Gets modified date (DOS format). /// - public ushort Date - { - get { return this.date; } - } + public ushort Date { get; } /// /// Gets modified time (DOS format). /// - public ushort Time - { - get { return this.time; } - } + public ushort Time { get; } /// /// Gets the size of the file in bytes. /// - public int Size - { - get { return this.size; } - } + public int Size { get; } } } diff --git a/src/WixToolset.Core/Cab/WixCreateCab.cs b/src/WixToolset.Core/Cab/WixCreateCab.cs index 8f985a43..4ebdd1c0 100644 --- a/src/WixToolset.Core/Cab/WixCreateCab.cs +++ b/src/WixToolset.Core/Cab/WixCreateCab.cs @@ -1,12 +1,12 @@ // 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.Cab +namespace WixToolset.Core.Cab { using System; using System.Globalization; using System.IO; using System.Runtime.InteropServices; - using WixToolset.Bind.Databases; + using WixToolset.Core.Bind; using WixToolset.Core.Native; using WixToolset.Data; diff --git a/src/WixToolset.Core/Cab/WixEnumerateCab.cs b/src/WixToolset.Core/Cab/WixEnumerateCab.cs index 017eeffb..0b4055d6 100644 --- a/src/WixToolset.Core/Cab/WixEnumerateCab.cs +++ b/src/WixToolset.Core/Cab/WixEnumerateCab.cs @@ -1,6 +1,6 @@ // 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.Cab +namespace WixToolset.Core.Cab { using System; using System.Collections.Generic; @@ -10,7 +10,7 @@ namespace WixToolset.Cab /// /// Wrapper class around interop with wixcab.dll to enumerate files from a cabinet. /// - internal sealed class WixEnumerateCab : IDisposable + public sealed class WixEnumerateCab : IDisposable { private bool disposed; private List fileInfoList; @@ -38,7 +38,7 @@ namespace WixToolset.Cab /// /// path to cabinet /// list of CabinetFileInfo - internal List Enumerate(string cabinetFile) + public List Enumerate(string cabinetFile) { this.fileInfoList = new List(); diff --git a/src/WixToolset.Core/Cab/WixExtractCab.cs b/src/WixToolset.Core/Cab/WixExtractCab.cs index debdaf15..e776b08e 100644 --- a/src/WixToolset.Core/Cab/WixExtractCab.cs +++ b/src/WixToolset.Core/Cab/WixExtractCab.cs @@ -1,9 +1,8 @@ // 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.Cab +namespace WixToolset.Core.Cab { using System; - using System.Runtime.InteropServices; using WixToolset.Core.Native; /// diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index afb9e829..32da5bcf 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -7,13 +7,14 @@ namespace WixToolset.Core using System.IO; using System.Linq; using WixToolset.Data; + using WixToolset.Data.Rows; using WixToolset.Extensibility; internal class BuildCommand : ICommandLineCommand { - public BuildCommand(ExtensionManager extensions, IEnumerable sources, IDictionary preprocessorVariables, IEnumerable locFiles, IEnumerable libraryFiles, string outputPath, OutputType outputType, IEnumerable cultures, bool bindFiles, IEnumerable bindPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile, string wixProjectFile) + public BuildCommand(ExtensionManager extensions, IEnumerable sources, IDictionary preprocessorVariables, IEnumerable locFiles, IEnumerable libraryFiles, string outputPath, OutputType outputType, string cabCachePath, IEnumerable cultures, bool bindFiles, IEnumerable bindPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile, string wixProjectFile) { - this.Extensions = extensions; + this.ExtensionManager = extensions; this.LocFiles = locFiles; this.LibraryFiles = libraryFiles; this.PreprocessorVariables = preprocessorVariables; @@ -21,6 +22,7 @@ namespace WixToolset.Core this.OutputPath = outputPath; this.OutputType = outputType; + this.CabCachePath = cabCachePath; this.Cultures = cultures; this.BindFiles = bindFiles; this.BindPaths = bindPaths; @@ -32,7 +34,7 @@ namespace WixToolset.Core this.WixProjectFile = wixProjectFile; } - public ExtensionManager Extensions { get; } + public ExtensionManager ExtensionManager { get; } public IEnumerable LocFiles { get; } @@ -46,6 +48,8 @@ namespace WixToolset.Core private OutputType OutputType { get; } + public string CabCachePath { get; } + public IEnumerable Cultures { get; } public bool BindFiles { get; } @@ -70,7 +74,9 @@ namespace WixToolset.Core if (this.OutputType == OutputType.Library) { - this.LibraryPhase(intermediates, tableDefinitions); + var library = this.LibraryPhase(intermediates, tableDefinitions); + + library?.Save(this.OutputPath); } else { @@ -105,51 +111,40 @@ namespace WixToolset.Core return intermediates; } - private void LibraryPhase(IEnumerable intermediates, TableDefinitionCollection tableDefinitions) + private Library LibraryPhase(IEnumerable intermediates, TableDefinitionCollection tableDefinitions) { var localizations = this.LoadLocalizationFiles(tableDefinitions).ToList(); // If there was an error adding localization files, then bail. if (Messaging.Instance.EncounteredError) { - return; + return null; } - var sections = intermediates.SelectMany(i => i.Sections).ToList(); - - LibraryBinaryFileResolver resolver = null; - - if (this.BindFiles) - { - resolver = new LibraryBinaryFileResolver(); - resolver.FileManagers = new List { new BinderFileManager() }; ; - resolver.VariableResolver = new WixVariableResolver(); - - BinderFileManagerCore core = new BinderFileManagerCore(); - core.AddBindPaths(this.BindPaths, BindStage.Normal); - - foreach (var fileManager in resolver.FileManagers) - { - fileManager.Core = core; - } - } + var resolver = CreateWixResolverWithVariables(null, null); - var librarian = new Librarian(); + var context = new LibraryContext(); + context.BindFiles = this.BindFiles; + context.BindPaths = this.BindPaths; + context.Extensions = this.ExtensionManager.Create(); + context.Localizations = localizations; + context.Sections = intermediates.SelectMany(i => i.Sections).ToList(); + context.WixVariableResolver = resolver; - var library = librarian.Combine(sections, localizations, resolver); + var librarian = new Librarian(context); - library?.Save(this.OutputPath); + return librarian.Combine(); } private Output LinkPhase(IEnumerable intermediates, TableDefinitionCollection tableDefinitions) { var sections = intermediates.SelectMany(i => i.Sections).ToList(); - sections.AddRange(SectionsFromLibraries(tableDefinitions)); + sections.AddRange(this.SectionsFromLibraries(tableDefinitions)); var linker = new Linker(); - foreach (var data in this.Extensions.Create()) + foreach (var data in this.ExtensionManager.Create()) { linker.AddExtensionData(data); } @@ -159,6 +154,40 @@ namespace WixToolset.Core return output; } + private void BindPhase(Output output, TableDefinitionCollection tableDefinitions) + { + var localizations = this.LoadLocalizationFiles(tableDefinitions).ToList(); + + var localizer = new Localizer(localizations); + + var resolver = CreateWixResolverWithVariables(localizer, output); + + var context = new BindContext(); + context.Messaging = Messaging.Instance; + context.ExtensionManager = this.ExtensionManager; + context.BindPaths = this.BindPaths ?? Array.Empty(); + //context.CabbingThreadCount = this.CabbingThreadCount; + context.CabCachePath = this.CabCachePath; + context.Codepage = localizer.Codepage; + //context.DefaultCompressionLevel = this.DefaultCompressionLevel; + //context.Ices = this.Ices; + context.IntermediateFolder = this.IntermediateFolder; + context.IntermediateRepresentation = output; + context.OutputPath = this.OutputPath; + context.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb"); + //context.SuppressIces = this.SuppressIces; + context.SuppressValidation = true; + //context.SuppressValidation = this.SuppressValidation; + context.WixVariableResolver = resolver; + context.ContentsFile = this.ContentsFile; + context.OutputsFile = this.OutputsFile; + context.BuiltOutputsFile = this.BuiltOutputsFile; + context.WixprojectFile = this.WixProjectFile; + + var binder = new Binder(context); + binder.Bind(); + } + private IEnumerable
SectionsFromLibraries(TableDefinitionCollection tableDefinitions) { var sections = new List
(); @@ -187,34 +216,6 @@ namespace WixToolset.Core return sections; } - private void BindPhase(Output output, TableDefinitionCollection tableDefinitions) - { - var localizations = this.LoadLocalizationFiles(tableDefinitions).ToList(); - - var localizer = new Localizer(localizations); - - var resolver = new WixVariableResolver(localizer); - - var binder = new Binder(); - binder.TempFilesLocation = this.IntermediateFolder; - binder.WixVariableResolver = resolver; - binder.SuppressValidation = true; - - binder.ContentsFile = this.ContentsFile; - binder.OutputsFile = this.OutputsFile; - binder.BuiltOutputsFile = this.BuiltOutputsFile; - binder.WixprojectFile = this.WixProjectFile; - - if (this.BindPaths != null) - { - binder.BindPaths.AddRange(this.BindPaths); - } - - binder.AddExtension(new BinderFileManager()); - - binder.Bind(output, this.OutputPath); - } - private IEnumerable LoadLocalizationFiles(TableDefinitionCollection tableDefinitions) { foreach (var loc in this.LocFiles) @@ -225,30 +226,21 @@ namespace WixToolset.Core } } - /// - /// File resolution mechanism to create binary library. - /// - private class LibraryBinaryFileResolver : ILibraryBinaryFileResolver + private static WixVariableResolver CreateWixResolverWithVariables(Localizer localizer, Output output) { - public IEnumerable FileManagers { get; set; } - - public WixVariableResolver VariableResolver { get; set; } + var resolver = new WixVariableResolver(localizer); - public string Resolve(SourceLineNumber sourceLineNumber, string table, string path) + // Gather all the wix variables. + Table wixVariableTable = output?.Tables["WixVariable"]; + if (null != wixVariableTable) { - string resolvedPath = this.VariableResolver.ResolveVariables(sourceLineNumber, path, false); - - foreach (IBinderFileManager fileManager in this.FileManagers) + foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) { - string finalPath = fileManager.ResolveFile(resolvedPath, table, sourceLineNumber, BindStage.Normal); - if (!String.IsNullOrEmpty(finalPath)) - { - return finalPath; - } + resolver.AddVariable(wixVariableRow); } - - return null; } + + return resolver; } } } diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs index a3a6831c..2f203ecb 100644 --- a/src/WixToolset.Core/CommandLine/CommandLine.cs +++ b/src/WixToolset.Core/CommandLine/CommandLine.cs @@ -6,6 +6,7 @@ namespace WixToolset.Core using System.Collections.Generic; using System.IO; using System.Linq; + using System.Reflection; using System.Text; using System.Text.RegularExpressions; using WixToolset.Data; @@ -71,6 +72,7 @@ namespace WixToolset.Core var intermediateFolder = String.Empty; + var cabCachePath = String.Empty; var cultures = new List(); var contentsFile = String.Empty; var outputsFile = String.Empty; @@ -98,6 +100,10 @@ namespace WixToolset.Core cmdline.GetNextArgumentOrError(bindPaths); return true; + case "cc": + cmdline.GetNextArgumentOrError(ref cabCachePath); + return true; + case "cultures": cmdline.GetNextArgumentOrError(cultures); return true; @@ -190,12 +196,14 @@ namespace WixToolset.Core { case Commands.Build: { + LoadStandardBackends(cli.ExtensionManager); + var sourceFiles = GatherSourceFiles(files, outputFolder); var variables = GatherPreprocessorVariables(defines); var bindPathList = GatherBindPaths(bindPaths); var extensions = cli.ExtensionManager; var type = CalculateOutputType(outputType, outputFile); - return new BuildCommand(extensions, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile, wixProjectFile); + return new BuildCommand(extensions, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile, wixProjectFile); } case Commands.Compile: @@ -209,6 +217,18 @@ namespace WixToolset.Core return null; } + private static void LoadStandardBackends(ExtensionManager extensionManager) + { + var folder = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); + + foreach (var backendAssemblyName in new[] { "WixToolset.Core.Burn.dll", "WixToolset.Core.WindowsInstaller.dll" }) + { + var path = Path.Combine(folder, backendAssemblyName); + + extensionManager.Load(path); + } + } + private static OutputType CalculateOutputType(string outputType, string outputFile) { if (String.IsNullOrEmpty(outputType)) diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index a2881984..28e7ee7b 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs @@ -17,7 +17,7 @@ namespace WixToolset /// /// Common Wix utility methods and types. /// - internal static class Common + public static class Common { //------------------------------------------------------------------------------------------------- // Layout of an Access Mask (from http://technet.microsoft.com/en-us/library/cc783530(WS.10).aspx) @@ -89,9 +89,7 @@ namespace WixToolset // FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) internal static readonly string[] FilePermissions = { "Read", "Write", "Append", "ReadExtendedAttributes", "WriteExtendedAttributes", "Execute", "FileAllRights", "ReadAttributes", "WriteAttributes" }; - internal static readonly string[] ReservedFileNames = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; - - internal static readonly Regex WixVariableRegex = new Regex(@"(\!|\$)\((?loc|wix|bind|bindpath)\.(?(?[_A-Za-z][0-9A-Za-z_]+)(\.(?[_A-Za-z][0-9A-Za-z_\.]*))?)(\=(?.+?))?\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); + public static readonly Regex WixVariableRegex = new Regex(@"(\!|\$)\((?loc|wix|bind|bindpath)\.(?(?[_A-Za-z][0-9A-Za-z_]+)(\.(?[_A-Za-z][0-9A-Za-z_\.]*))?)(\=(?.+?))?\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); internal const char CustomRowFieldSeparator = '\x85'; @@ -170,15 +168,14 @@ namespace WixToolset /// is null. /// The value doesn't not represent a valid code page name or integer value. /// The code page is invalid for summary information. - internal static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) + public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) { - int codePage; - Encoding encoding; - try { + Encoding encoding; + // check if a integer as a string was passed - if (Int32.TryParse(value, out codePage)) + if (Int32.TryParse(value, out int codePage)) { if (0 == codePage) { @@ -366,9 +363,9 @@ namespace WixToolset /// Generate a new Windows Installer-friendly guid. ///
/// A new guid. - internal static string GenerateGuid() + public static string GenerateGuid() { - return Guid.NewGuid().ToString("B").ToUpper(CultureInfo.InvariantCulture); + return Guid.NewGuid().ToString("B").ToUpperInvariant(); } /// @@ -465,7 +462,7 @@ namespace WixToolset } } - internal static string GetFileHash(string path) + public static string GetFileHash(string path) { using (SHA1Managed managed = new SHA1Managed()) { @@ -477,6 +474,147 @@ namespace WixToolset } } + /// + /// Takes an id, and demodularizes it (if possible). + /// + /// + /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id. + /// + /// The type of the output to bind. + /// The modularization GUID. + /// The id to demodularize. + /// The demodularized id. + public static string Demodularize(OutputType outputType, string modularizationGuid, string id) + { + if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal)) + { + id = id.Substring(0, id.Length - 37); + } + + return id; + } + + /// + /// Get the source/target and short/long file names from an MSI Filename column. + /// + /// The Filename value. + /// An array of strings of length 4. The contents are: short target, long target, short source, and long source. + /// + /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings. + /// However, the returned array will always be of length 4. + /// + public static string[] GetNames(string value) + { + string[] names = new string[4]; + int targetSeparator = value.IndexOf(":", StringComparison.Ordinal); + + // split source and target + string sourceName = null; + string targetName = value; + if (0 <= targetSeparator) + { + sourceName = value.Substring(targetSeparator + 1); + targetName = value.Substring(0, targetSeparator); + } + + // split the source short and long names + string sourceLongName = null; + if (null != sourceName) + { + int sourceLongNameSeparator = sourceName.IndexOf("|", StringComparison.Ordinal); + if (0 <= sourceLongNameSeparator) + { + sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); + sourceName = sourceName.Substring(0, sourceLongNameSeparator); + } + } + + // split the target short and long names + int targetLongNameSeparator = targetName.IndexOf("|", StringComparison.Ordinal); + string targetLongName = null; + if (0 <= targetLongNameSeparator) + { + targetLongName = targetName.Substring(targetLongNameSeparator + 1); + targetName = targetName.Substring(0, targetLongNameSeparator); + } + + // remove the long source name when its identical to the long source name + if (null != sourceName && sourceName == sourceLongName) + { + sourceLongName = null; + } + + // remove the long target name when its identical to the long target name + if (null != targetName && targetName == targetLongName) + { + targetLongName = null; + } + + // remove the source names when they are identical to the target names + if (sourceName == targetName && sourceLongName == targetLongName) + { + sourceName = null; + sourceLongName = null; + } + + // target name(s) + if ("." != targetName) + { + names[0] = targetName; + } + + if (null != targetLongName && "." != targetLongName) + { + names[1] = targetLongName; + } + + // source name(s) + if (null != sourceName) + { + names[2] = sourceName; + } + + if (null != sourceLongName && "." != sourceLongName) + { + names[3] = sourceLongName; + } + + return names; + } + + /// + /// Get a source/target and short/long file name from an MSI Filename column. + /// + /// The Filename value. + /// true to get a source name; false to get a target name + /// true to get a long name; false to get a short name + /// The name. + public static string GetName(string value, bool source, bool longName) + { + string[] names = GetNames(value); + + if (source) + { + if (longName && null != names[3]) + { + return names[3]; + } + else if (null != names[2]) + { + return names[2]; + } + } + + if (longName && null != names[1]) + { + return names[1]; + } + else + { + return names[0]; + } + } + /// /// Get an attribute value. /// diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ed7cb60e..d085e788 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -11,11 +11,11 @@ namespace WixToolset using System.IO; using System.Text.RegularExpressions; using System.Xml.Linq; + using WixToolset.Core; + using WixToolset.Core.Native; using WixToolset.Data; using WixToolset.Data.Rows; using WixToolset.Extensibility; - using WixToolset.Msi; - using WixToolset.Core.Native; using Wix = WixToolset.Data.Serialize; /// @@ -158,10 +158,7 @@ namespace WixToolset [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] public Intermediate Compile(XDocument source) { - if (null == source) - { - throw new ArgumentNullException("source"); - } + if (null == source) throw new ArgumentNullException(nameof(source)); bool encounteredError = false; @@ -220,9 +217,7 @@ namespace WixToolset { if (field.Data is string) { - bool isDefault = false; - bool delayedResolve = false; - field.Data = this.componentIdPlaceholdersResolver.ResolveVariables(row.SourceLineNumbers, (string)field.Data, false, false, ref isDefault, ref delayedResolve); + field.Data = this.componentIdPlaceholdersResolver.ResolveVariables(row.SourceLineNumbers, (string)field.Data, false, false, out var defaultIgnored, out var delayedIgnored); } } } @@ -470,7 +465,8 @@ namespace WixToolset case "Advertise": appIdAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; - case "Description": description = this.core.GetAttributeValue(sourceLineNumbers, attrib); + case "Description": + description = this.core.GetAttributeValue(sourceLineNumbers, attrib); break; case "DllSurrogate": dllSurrogate = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); @@ -9471,13 +9467,13 @@ namespace WixToolset targetProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib); break; case "ApiPatchingSymbolNoImagehlpFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP : 0; + apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP : 0; break; case "ApiPatchingSymbolNoFailuresFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES : 0; + apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES : 0; break; case "ApiPatchingSymbolUndecoratedTooFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO : 0; + apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO : 0; break; case "OptimizePatchSizeForLargeFiles": optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); @@ -11802,7 +11798,7 @@ namespace WixToolset private void ParseProductElement(XElement node) { SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - int codepage = 0; + int codepage = 65001; string productCode = null; string upgradeCode = null; string manufacturer = null; diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 8640a2da..8f4703f7 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -45,10 +45,6 @@ namespace WixToolset internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; - public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB - public const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB - public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) - private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled); private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * " @@ -67,6 +63,11 @@ namespace WixToolset private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?public|internal|protected|private)\s+)?(?[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); + public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB + public const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB + public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) + + // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) private static readonly List BuiltinBundleVariables = new List( new string[] { diff --git a/src/WixToolset.Core/Data/messages.xml b/src/WixToolset.Core/Data/messages.xml index edc98147..d981e2d1 100644 --- a/src/WixToolset.Core/Data/messages.xml +++ b/src/WixToolset.Core/Data/messages.xml @@ -957,12 +957,6 @@ - - - Invalid file name '{0}'. - - - A circular reference of groups was detected. The infinite loop includes: {0}. Group references must form a directed acyclic graph. @@ -2138,12 +2132,6 @@ This patch is not uninstallable. The 'Patch' element's attribute 'AllowRemoval' should be set to 'no'. - - - '{0}' is too long, the fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters. - - - '{0}' is too large, file size must be less than 2147483648. diff --git a/src/WixToolset.Core/Decompiler.cs b/src/WixToolset.Core/Decompiler.cs index 249b5788..e72b0104 100644 --- a/src/WixToolset.Core/Decompiler.cs +++ b/src/WixToolset.Core/Decompiler.cs @@ -3,7 +3,6 @@ namespace WixToolset { using System; - using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; @@ -15,9 +14,9 @@ namespace WixToolset using WixToolset.Data; using WixToolset.Data.Rows; using WixToolset.Extensibility; - using WixToolset.Msi; using WixToolset.Core.Native; using Wix = WixToolset.Data.Serialize; + using WixToolset.Core; /// /// Decompiles an msi database into WiX source. @@ -5201,7 +5200,7 @@ namespace WixToolset directory.Id = Convert.ToString(row[0]); - string[] names = Installer.GetNames(Convert.ToString(row[2])); + string[] names = Common.GetNames(Convert.ToString(row[2])); if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) { @@ -5319,7 +5318,7 @@ namespace WixToolset if (null != row[3]) { - string[] names = Installer.GetNames(Convert.ToString(row[3])); + string[] names = Common.GetNames(Convert.ToString(row[3])); if (null != names[0] && null != names[1]) { copyFile.DestinationShortName = names[0]; @@ -5788,7 +5787,7 @@ namespace WixToolset file.Id = fileRow.File; - string[] names = Installer.GetNames(fileRow.FileName); + string[] names = Common.GetNames(fileRow.FileName); if (null != names[0] && null != names[1]) { file.ShortName = names[0]; @@ -5974,7 +5973,7 @@ namespace WixToolset iniFile.Id = Convert.ToString(row[0]); - string[] names = Installer.GetNames(Convert.ToString(row[1])); + string[] names = Common.GetNames(Convert.ToString(row[1])); if (null != names[0]) { @@ -6044,7 +6043,7 @@ namespace WixToolset iniFileSearch.Id = Convert.ToString(row[0]); - string[] names = Installer.GetNames(Convert.ToString(row[1])); + string[] names = Common.GetNames(Convert.ToString(row[1])); if (null != names[0] && null != names[1]) { iniFileSearch.ShortName = names[0]; @@ -6681,7 +6680,7 @@ namespace WixToolset if (null != row[3]) { - string[] names = Installer.GetNames(Convert.ToString(row[3])); + string[] names = Common.GetNames(Convert.ToString(row[3])); if (null != names[0] && null != names[1]) { copyFile.DestinationShortName = names[0]; @@ -8007,7 +8006,7 @@ namespace WixToolset removeFile.Id = Convert.ToString(row[0]); - string[] names = Installer.GetNames(Convert.ToString(row[2])); + string[] names = Common.GetNames(Convert.ToString(row[2])); if (null != names[0] && null != names[1]) { removeFile.ShortName = names[0]; @@ -8062,7 +8061,7 @@ namespace WixToolset iniFile.Id = Convert.ToString(row[0]); - string[] names = Installer.GetNames(Convert.ToString(row[1])); + string[] names = Common.GetNames(Convert.ToString(row[1])); if (null != names[0] && null != names[1]) { iniFile.ShortName = names[0]; @@ -8531,7 +8530,7 @@ namespace WixToolset shortcut.Directory = Convert.ToString(row[1]); - string[] names = Installer.GetNames(Convert.ToString(row[2])); + string[] names = Common.GetNames(Convert.ToString(row[2])); if (null != names[0] && null != names[1]) { shortcut.ShortName = names[0]; @@ -8654,7 +8653,7 @@ namespace WixToolset fileSearch.Id = Convert.ToString(row[0]); - string[] names = Installer.GetNames(Convert.ToString(row[1])); + string[] names = Common.GetNames(Convert.ToString(row[1])); if (null != names[0]) { // it is permissable to just have a long name diff --git a/src/WixToolset.Core/Differ.cs b/src/WixToolset.Core/Differ.cs deleted file mode 100644 index 71a64327..00000000 --- a/src/WixToolset.Core/Differ.cs +++ /dev/null @@ -1,621 +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 -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using WixToolset.Data; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - using WixToolset.Msi; - - /// - /// Creates a transform by diffing two outputs. - /// - public sealed class Differ : IMessageHandler - { - private List inspectorExtensions; - private bool showPedanticMessages; - private bool suppressKeepingSpecialRows; - private bool preserveUnchangedRows; - private const char sectionDelimiter = '/'; - private SummaryInformationStreams transformSummaryInfo; - - /// - /// Instantiates a new Differ class. - /// - public Differ() - { - this.inspectorExtensions = new List(); - } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages - { - get { return this.showPedanticMessages; } - set { this.showPedanticMessages = value; } - } - - /// - /// Gets or sets the option to suppress keeping special rows. - /// - /// The option to suppress keeping special rows. - public bool SuppressKeepingSpecialRows - { - get { return this.suppressKeepingSpecialRows; } - set { this.suppressKeepingSpecialRows = value; } - } - - /// - /// 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. - public bool PreserveUnchangedRows - { - get { return this.preserveUnchangedRows; } - set { this.preserveUnchangedRows = value; } - } - - /// - /// Adds an extension. - /// - /// The extension to add. - public void AddExtension(IInspectorExtension extension) - { - this.inspectorExtensions.Add(extension); - } - - /// - /// Creates a transform by diffing two outputs. - /// - /// The target output. - /// The updated output. - /// The transform. - public Output Diff(Output targetOutput, Output updatedOutput) - { - return Diff(targetOutput, updatedOutput, 0); - } - - /// - /// Creates a transform by diffing two outputs. - /// - /// The target output. - /// The updated output. - /// - /// The transform. - public Output Diff(Output targetOutput, Output updatedOutput, TransformFlags validationFlags) - { - Output transform = new Output(null); - transform.Type = OutputType.Transform; - transform.Codepage = updatedOutput.Codepage; - this.transformSummaryInfo = new SummaryInformationStreams(); - - // compare the codepages - if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) - { - this.OnMessage(WixErrors.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); - if (null != updatedOutput.SourceLineNumbers) - { - this.OnMessage(WixErrors.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); - } - } - - // compare the output types - if (targetOutput.Type != updatedOutput.Type) - { - throw new WixException(WixErrors.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); - } - - // compare the contents of the tables - foreach (Table targetTable in targetOutput.Tables) - { - Table updatedTable = updatedOutput.Tables[targetTable.Name]; - TableOperation operation = TableOperation.None; - - List rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); - - if (TableOperation.Drop == operation) - { - Table droppedTable = transform.EnsureTable(targetTable.Definition); - droppedTable.Operation = TableOperation.Drop; - } - else if (TableOperation.None == operation) - { - Table modified = transform.EnsureTable(updatedTable.Definition); - rows.ForEach(r => modified.Rows.Add(r)); - } - } - - // added tables - foreach (Table updatedTable in updatedOutput.Tables) - { - if (null == targetOutput.Tables[updatedTable.Name]) - { - Table addedTable = transform.EnsureTable(updatedTable.Definition); - addedTable.Operation = TableOperation.Add; - - foreach (Row updatedRow in updatedTable.Rows) - { - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - addedTable.Rows.Add(updatedRow); - } - } - } - - // set summary information properties - if (!this.suppressKeepingSpecialRows) - { - Table summaryInfoTable = transform.Tables["_SummaryInformation"]; - this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); - } - - // inspect the transform - InspectorCore inspectorCore = new InspectorCore(); - foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) - { - inspectorExtension.Core = inspectorCore; - inspectorExtension.InspectOutput(transform); - - // reset - inspectorExtension.Core = null; - } - - return transform; - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } - - /// - /// Add a row to the using the primary key. - /// - /// The indexed rows. - /// The row to index. - private void AddIndexedRow(IDictionary index, Row row) - { - string primaryKey = row.GetPrimaryKey('/'); - if (null != primaryKey) - { - // Overriding WixActionRows have a primary key defined and take precedence in the index. - if (row is WixActionRow) - { - WixActionRow currentRow = (WixActionRow)row; - if (index.Contains(primaryKey)) - { - // If the current row is not overridable, see if the indexed row is. - if (!currentRow.Overridable) - { - WixActionRow indexedRow = index[primaryKey] as WixActionRow; - if (null != indexedRow && indexedRow.Overridable) - { - // The indexed key is overridable and should be replaced - // (not removed and re-added which results in two Array.Copy - // operations for SortedList, or may be re-hashing in other - // implementations of IDictionary). - index[primaryKey] = currentRow; - } - } - - // If we got this far, the row does not need to be indexed. - return; - } - } - - // Nothing else should be added more than once. - if (!index.Contains(primaryKey)) - { - index.Add(primaryKey, row); - } - else if (this.showPedanticMessages) - { - this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); - } - } - else // use the string representation of the row as its primary key (it may not be unique) - { - // this is provided for compatibility with unreal tables with no primary key - // all real tables must specify at least one column as the primary key - primaryKey = row.ToString(); - index[primaryKey] = row; - } - } - - private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow) - { - Row comparedRow = null; - keepRow = false; - operation = RowOperation.None; - - if (null == targetRow ^ null == updatedRow) - { - if (null == targetRow) - { - operation = updatedRow.Operation = RowOperation.Add; - comparedRow = updatedRow; - } - else if (null == updatedRow) - { - operation = targetRow.Operation = RowOperation.Delete; - targetRow.SectionId = targetRow.SectionId + sectionDelimiter; - comparedRow = targetRow; - keepRow = true; - } - } - else // possibly modified - { - updatedRow.Operation = RowOperation.None; - if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) - { - // 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; - comparedRow = updatedRow; - keepRow = true; - operation = RowOperation.Modify; - } - } - else - { - if (this.preserveUnchangedRows) - { - keepRow = true; - } - - for (int i = 0; i < updatedRow.Fields.Length; i++) - { - ColumnDefinition columnDefinition = updatedRow.Fields[i].Column; - - if (!columnDefinition.PrimaryKey) - { - bool modified = false; - - if (i >= targetRow.Fields.Length) - { - columnDefinition.Added = true; - modified = true; - } - else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) - { - if (null == targetRow[i] ^ null == updatedRow[i]) - { - modified = true; - } - else if (null != targetRow[i] && null != updatedRow[i]) - { - modified = ((int)targetRow[i] != (int)updatedRow[i]); - } - } - else if (ColumnType.Preserved == columnDefinition.Type) - { - updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data; - - // keep rows containing preserved fields so the historical data is available to the binder - keepRow = !this.suppressKeepingSpecialRows; - } - else if (ColumnType.Object == columnDefinition.Type) - { - ObjectField targetObjectField = (ObjectField)targetRow.Fields[i]; - ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i]; - - 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 - updatedObjectField.PreviousData = (string)targetObjectField.Data; - - // always remember the unresolved data for target build - updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData; - - // keep rows containing object fields so the files can be compared in the binder - keepRow = !this.suppressKeepingSpecialRows; - } - else - { - modified = ((string)targetRow[i] != (string)updatedRow[i]); - } - - if (modified) - { - if (null != updatedRow.Fields[i].PreviousData) - { - updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString(); - } - - updatedRow.Fields[i].Modified = true; - operation = updatedRow.Operation = RowOperation.Modify; - keepRow = true; - } - } - } - - if (keepRow) - { - comparedRow = updatedRow; - comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; - } - } - } - - return comparedRow; - } - - private List CompareTables(Output targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) - { - List rows = new List(); - operation = TableOperation.None; - - // dropped tables - if (null == updatedTable ^ null == targetTable) - { - if (null == targetTable) - { - operation = TableOperation.Add; - rows.AddRange(updatedTable.Rows); - } - else if (null == updatedTable) - { - operation = TableOperation.Drop; - } - } - else // possibly modified tables - { - SortedList updatedPrimaryKeys = new SortedList(); - SortedList targetPrimaryKeys = new SortedList(); - - // compare the table definitions - if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) - { - // continue to the next table; may be more mismatches - this.OnMessage(WixErrors.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); - } - else - { - this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); - - // diff the target and updated rows - foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys) - { - string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key; - bool keepRow = false; - RowOperation rowOperation = RowOperation.None; - - Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow); - - if (keepRow) - { - rows.Add(compared); - } - } - - // find the inserted rows - foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys) - { - string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key; - - if (!targetPrimaryKeys.Contains(updatedPrimaryKey)) - { - Row updatedRow = (Row)updatedPrimaryKeyEntry.Value; - - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - rows.Add(updatedRow); - } - } - } - } - - return rows; - } - - private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys) - { - // index the target rows - foreach (Row row in targetTable.Rows) - { - this.AddIndexedRow(targetPrimaryKeys, row); - - if ("Property" == targetTable.Name) - { - if ("ProductCode" == (string)row[0]) - { - this.transformSummaryInfo.TargetProductCode = (string)row[1]; - if ("*" == this.transformSummaryInfo.TargetProductCode) - { - this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == (string)row[0]) - { - this.transformSummaryInfo.TargetProductVersion = (string)row[1]; - } - else if ("UpgradeCode" == (string)row[0]) - { - this.transformSummaryInfo.TargetUpgradeCode = (string)row[1]; - } - } - else if ("_SummaryInformation" == targetTable.Name) - { - if (1 == (int)row[0]) // PID_CODEPAGE - { - this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1]; - } - else if (7 == (int)row[0]) // PID_TEMPLATE - { - this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1]; - } - else if (14 == (int)row[0]) // PID_PAGECOUNT - { - this.transformSummaryInfo.TargetMinimumVersion = (string)row[1]; - } - } - } - - // index the updated rows - foreach (Row row in updatedTable.Rows) - { - this.AddIndexedRow(updatedPrimaryKeys, row); - - if ("Property" == updatedTable.Name) - { - if ("ProductCode" == (string)row[0]) - { - this.transformSummaryInfo.UpdatedProductCode = (string)row[1]; - if ("*" == this.transformSummaryInfo.UpdatedProductCode) - { - this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == (string)row[0]) - { - this.transformSummaryInfo.UpdatedProductVersion = (string)row[1]; - } - } - else if ("_SummaryInformation" == updatedTable.Name) - { - if (1 == (int)row[0]) // PID_CODEPAGE - { - this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1]; - } - else if (7 == (int)row[0]) // PID_TEMPLATE - { - this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1]; - } - else if (14 == (int)row[0]) // PID_PAGECOUNT - { - this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1]; - } - } - } - } - - private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) - { - // calculate the minimum version of MSI required to process the transform - int targetMin; - int updatedMin; - int minimumVersion = 100; - - if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin)) - { - minimumVersion = Math.Max(targetMin, updatedMin); - } - - Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count); - foreach (Row row in summaryInfoTable.Rows) - { - summaryRows[row[0]] = row; - - if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) - { - row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; - row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; - } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0]) - { - row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) - { - row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) - { - row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); - } - else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0]) - { - row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - else if ((int)SummaryInformation.Transform.Security == (int)row[0]) - { - row[1] = "4"; - } - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; - summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.Security; - summaryRow[1] = "4"; - } - } - - private class SummaryInformationStreams - { - public string TargetSummaryInfoCodepage - { get; set; } - - public string TargetPlatformAndLanguage - { get; set; } - - public string TargetProductCode - { get; set; } - - public string TargetProductVersion - { get; set; } - - public string TargetUpgradeCode - { get; set; } - - public string TargetMinimumVersion - { get; set; } - - public string UpdatedSummaryInfoCodepage - { get; set; } - - public string UpdatedPlatformAndLanguage - { get; set; } - - public string UpdatedProductCode - { get; set; } - - public string UpdatedProductVersion - { get; set; } - - public string UpdatedMinimumVersion - { get; set; } - } - } -} diff --git a/src/WixToolset.Core/Extensibility/HeatExtension.cs b/src/WixToolset.Core/Extensibility/HeatExtension.cs index 5e292220..48e1a93b 100644 --- a/src/WixToolset.Core/Extensibility/HeatExtension.cs +++ b/src/WixToolset.Core/Extensibility/HeatExtension.cs @@ -3,14 +3,10 @@ namespace WixToolset.Extensibility { using System; - using System.Collections.Generic; using System.IO; using System.Reflection; - using WixToolset; using WixToolset.Data; - using WixToolset.Extensibilty; using WixToolset.Tools; - using Wix = WixToolset.Data.Serialize; /// /// A command line option. diff --git a/src/WixToolset.Core/Extensibility/IHeatCore.cs b/src/WixToolset.Core/Extensibility/IHeatCore.cs index bc853b24..dbfc8929 100644 --- a/src/WixToolset.Core/Extensibility/IHeatCore.cs +++ b/src/WixToolset.Core/Extensibility/IHeatCore.cs @@ -1,6 +1,6 @@ // 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.Extensibilty +namespace WixToolset.Extensibility { using WixToolset.Data; diff --git a/src/WixToolset.Core/Extensibility/ValidatorExtension.cs b/src/WixToolset.Core/Extensibility/ValidatorExtension.cs deleted file mode 100644 index 44ec3106..00000000 --- a/src/WixToolset.Core/Extensibility/ValidatorExtension.cs +++ /dev/null @@ -1,299 +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.Extensibility -{ - using System; - using System.Collections; - using WixToolset.Data; - - /// - /// Base class for creating a validator extension. This default implementation - /// will fire and event with the ICE name and description. - /// - public class ValidatorExtension : IMessageHandler - { - private string databaseFile; - private Hashtable indexedSourceLineNumbers; - private Output output; - private SourceLineNumber sourceLineNumbers; - - /// - /// Instantiate a new . - /// - public ValidatorExtension() - { - } - - /// - /// Gets or sets the path to the database to validate. - /// - /// The path to the database to validate. - public string DatabaseFile - { - get { return this.databaseFile; } - set { this.databaseFile = value; } - } - - /// - /// Gets or sets the for finding source line information. - /// - /// The for finding source line information. - public Output Output - { - get { return this.output; } - set { this.output = value; } - } - - /// - /// Called at the beginning of the validation of a database file. - /// - /// - /// The will set - /// before calling InitializeValidator. - /// Notes to Inheritors: When overriding - /// InitializeValidator in a derived class, be sure to call - /// the base class's InitializeValidator to thoroughly - /// initialize the extension. - /// - public virtual void InitializeValidator() - { - if (this.databaseFile != null) - { - this.sourceLineNumbers = new SourceLineNumber(databaseFile); - } - } - - /// - /// Called at the end of the validation of a database file. - /// - /// - /// The default implementation will nullify source lines. - /// Notes to Inheritors: When overriding - /// FinalizeValidator in a derived class, be sure to call - /// the base class's FinalizeValidator to thoroughly - /// finalize the extension. - /// - public virtual void FinalizeValidator() - { - this.sourceLineNumbers = null; - } - - /// - /// Logs a message from the . - /// - /// A of tab-delmited tokens - /// in the validation message. - public virtual void Log(string message) - { - this.Log(message, null); - } - - /// - /// Logs a message from the . - /// - /// A of tab-delmited tokens - /// in the validation message. - /// The name of the action to which the message - /// belongs. - /// The message cannot be null. - /// - /// The message does not contain four (4) - /// or more tab-delimited tokens. - /// - /// a tab-delimited set of tokens, - /// formatted according to Windows Installer guidelines for ICE - /// message. The following table lists what each token by index - /// should mean. - /// a name that represents the ICE - /// action that was executed (e.g. 'ICE08'). - /// - /// - /// Index - /// Description - /// - /// - /// 0 - /// Name of the ICE. - /// - /// - /// 1 - /// Message type. See the following list. - /// - /// - /// 2 - /// Detailed description. - /// - /// - /// 3 - /// Help URL or location. - /// - /// - /// 4 - /// Table name. - /// - /// - /// 5 - /// Column name. - /// - /// - /// 6 - /// This and remaining fields are primary keys - /// to identify a row. - /// - /// - /// The message types are one of the following value. - /// - /// - /// Value - /// Message Type - /// - /// - /// 0 - /// Failure message reporting the failure of the - /// ICE custom action. - /// - /// - /// 1 - /// Error message reporting database authoring that - /// case incorrect behavior. - /// - /// - /// 2 - /// Warning message reporting database authoring that - /// causes incorrect behavior in certain cases. Warnings can also - /// report unexpected side-effects of database authoring. - /// - /// - /// - /// 3 - /// Informational message. - /// - /// - /// - public virtual void Log(string message, string action) - { - if (message == null) - { - throw new ArgumentNullException("message"); - } - - string[] messageParts = message.Split('\t'); - if (3 > messageParts.Length) - { - if (null == action) - { - throw new WixException(WixErrors.UnexpectedExternalUIMessage(message)); - } - else - { - throw new WixException(WixErrors.UnexpectedExternalUIMessage(message, action)); - } - } - - SourceLineNumber messageSourceLineNumbers = null; - if (6 < messageParts.Length) - { - string[] primaryKeys = new string[messageParts.Length - 6]; - - Array.Copy(messageParts, 6, primaryKeys, 0, primaryKeys.Length); - - messageSourceLineNumbers = this.GetSourceLineNumbers(messageParts[4], primaryKeys); - } - else // use the file name as the source line information - { - messageSourceLineNumbers = this.sourceLineNumbers; - } - - switch (messageParts[1]) - { - case "0": - case "1": - this.OnMessage(WixErrors.ValidationError(messageSourceLineNumbers, messageParts[0], messageParts[2])); - break; - case "2": - this.OnMessage(WixWarnings.ValidationWarning(messageSourceLineNumbers, messageParts[0], messageParts[2])); - break; - case "3": - this.OnMessage(WixVerboses.ValidationInfo(messageParts[0], messageParts[2])); - break; - default: - throw new WixException(WixErrors.InvalidValidatorMessageType(messageParts[1])); - } - } - - /// - /// Gets the source line information (if available) for a row by its table name and primary key. - /// - /// The table name of the row. - /// The primary keys of the row. - /// The source line number information if found; null otherwise. - protected SourceLineNumber GetSourceLineNumbers(string tableName, string[] primaryKeys) - { - // source line information only exists if an output file was supplied - if (null != this.output) - { - // index the source line information if it hasn't been indexed already - if (null == this.indexedSourceLineNumbers) - { - this.indexedSourceLineNumbers = new Hashtable(); - - // index each real table - foreach (Table table in this.output.Tables) - { - // skip unreal tables - if (table.Definition.Unreal) - { - continue; - } - - // index each row - foreach (Row row in table.Rows) - { - // skip rows that don't contain source line information - if (null == row.SourceLineNumbers) - { - continue; - } - - // index the row using its table name and primary key - string primaryKey = row.GetPrimaryKey(';'); - if (null != primaryKey) - { - string key = String.Concat(table.Name, ":", primaryKey); - - if (this.indexedSourceLineNumbers.ContainsKey(key)) - { - this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); - } - else - { - this.indexedSourceLineNumbers.Add(key, row.SourceLineNumbers); - } - } - } - } - } - - return (SourceLineNumber)this.indexedSourceLineNumbers[String.Concat(tableName, ":", String.Join(";", primaryKeys))]; - } - - // use the file name as the source line information - return this.sourceLineNumbers; - } - - /// - /// Sends a message to the delegate if there is one. - /// - /// Message event arguments. - /// - /// Notes to Inheritors: When overriding OnMessage - /// in a derived class, be sure to call the base class's - /// OnMessage method so that registered delegates recieve - /// the event. - /// - public virtual void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } - } -} diff --git a/src/WixToolset.Core/ExtensionManager.cs b/src/WixToolset.Core/ExtensionManager.cs index 45cb65ec..7e40571b 100644 --- a/src/WixToolset.Core/ExtensionManager.cs +++ b/src/WixToolset.Core/ExtensionManager.cs @@ -8,8 +8,9 @@ namespace WixToolset using System.Linq; using System.Reflection; using WixToolset.Data; + using WixToolset.Extensibility; - public class ExtensionManager + public class ExtensionManager : IExtensionManager { private List extensionAssemblies = new List(); @@ -67,8 +68,7 @@ namespace WixToolset /// Extensions created of the specified type. public IEnumerable Create() where T : class { - var extensionType = typeof(T); - var types = this.extensionAssemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && extensionType.IsAssignableFrom(t))); + var types = this.extensionAssemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(T).IsAssignableFrom(t))); return types.Select(t => (T)Activator.CreateInstance(t)).ToList(); } diff --git a/src/WixToolset.Core/HeatCore.cs b/src/WixToolset.Core/HeatCore.cs index 5c5defe8..01233c40 100644 --- a/src/WixToolset.Core/HeatCore.cs +++ b/src/WixToolset.Core/HeatCore.cs @@ -2,11 +2,8 @@ namespace WixToolset.Tools { - using System; - using System.Reflection; using WixToolset.Data; - using WixToolset.Extensibilty; - using Wix = WixToolset.Data.Serialize; + using WixToolset.Extensibility; /// /// The WiX Toolset Harvester application core. diff --git a/src/WixToolset.Core/IncribeContext.cs b/src/WixToolset.Core/IncribeContext.cs new file mode 100644 index 00000000..604ba5d1 --- /dev/null +++ b/src/WixToolset.Core/IncribeContext.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class InscribeContext : IInscribeContext + { + public Messaging Messaging { get; } = Messaging.Instance; + + public string IntermediateFolder { get; set; } + + public string InputFilePath { get; set; } + + public string SignedEngineFile { get; set; } + + public string OutputFile { get; set; } + } +} diff --git a/src/WixToolset.Core/Inscriber.cs b/src/WixToolset.Core/Inscriber.cs index 5b467ec1..f01e0629 100644 --- a/src/WixToolset.Core/Inscriber.cs +++ b/src/WixToolset.Core/Inscriber.cs @@ -2,17 +2,8 @@ namespace WixToolset { - using System; - using System.CodeDom.Compiler; - using System.Collections.Generic; - using System.Globalization; using System.IO; - using System.Runtime.InteropServices; - using System.Security.Cryptography.X509Certificates; - using WixToolset.Bind.Bundles; using WixToolset.Data; - using WixToolset.Msi; - using WixToolset.Core.Native; /// /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. @@ -81,41 +72,41 @@ namespace WixToolset /// True if bundle was updated. public bool InscribeBundleEngine(string bundleFile, string outputFile) { - string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_unsigned.exe"); - - using (BurnReader reader = BurnReader.Open(bundleFile)) - using (FileStream writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) - { - reader.Stream.Seek(0, SeekOrigin.Begin); - - byte[] buffer = new byte[4 * 1024]; - int total = 0; - int read = 0; - do - { - read = Math.Min(buffer.Length, (int)reader.EngineSize - total); - - read = reader.Stream.Read(buffer, 0, read); - writer.Write(buffer, 0, read); - - total += read; - } while (total < reader.EngineSize && 0 < read); - - if (total != reader.EngineSize) - { - throw new InvalidOperationException("Failed to copy engine out of bundle."); - } - - // TODO: update writer with detached container signatures. - } - - Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); - if (File.Exists(outputFile)) - { - File.Delete(outputFile); - } - File.Move(tempFile, outputFile); - WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); + //string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_unsigned.exe"); + + //using (BurnReader reader = BurnReader.Open(bundleFile)) + //using (FileStream writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) + //{ + // reader.Stream.Seek(0, SeekOrigin.Begin); + + // byte[] buffer = new byte[4 * 1024]; + // int total = 0; + // int read = 0; + // do + // { + // read = Math.Min(buffer.Length, (int)reader.EngineSize - total); + + // read = reader.Stream.Read(buffer, 0, read); + // writer.Write(buffer, 0, read); + + // total += read; + // } while (total < reader.EngineSize && 0 < read); + + // if (total != reader.EngineSize) + // { + // throw new InvalidOperationException("Failed to copy engine out of bundle."); + // } + + // // TODO: update writer with detached container signatures. + //} + + //Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); + //if (File.Exists(outputFile)) + //{ + // File.Delete(outputFile); + //} + //File.Move(tempFile, outputFile); + //WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); return true; } @@ -129,36 +120,37 @@ namespace WixToolset /// True if bundle was updated. public bool InscribeBundle(string bundleFile, string signedEngineFile, string outputFile) { - bool inscribed = false; - string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_signed.exe"); - - using (BurnReader reader = BurnReader.Open(bundleFile)) - { - File.Copy(signedEngineFile, tempFile, true); - - // If there was an attached container on the original (unsigned) bundle, put it back. - if (reader.AttachedContainerSize > 0) - { - reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); - - using (BurnWriter writer = BurnWriter.Open(tempFile)) - { - writer.RememberThenResetSignature(); - writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); - inscribed = true; - } - } - } - - Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); - if (File.Exists(outputFile)) - { - File.Delete(outputFile); - } - File.Move(tempFile, outputFile); - WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); - - return inscribed; + //bool inscribed = false; + //string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_signed.exe"); + + //using (BurnReader reader = BurnReader.Open(bundleFile)) + //{ + // File.Copy(signedEngineFile, tempFile, true); + + // // If there was an attached container on the original (unsigned) bundle, put it back. + // if (reader.AttachedContainerSize > 0) + // { + // reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); + + // using (BurnWriter writer = BurnWriter.Open(tempFile)) + // { + // writer.RememberThenResetSignature(); + // writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); + // inscribed = true; + // } + // } + //} + + //Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); + //if (File.Exists(outputFile)) + //{ + // File.Delete(outputFile); + //} + //File.Move(tempFile, outputFile); + //WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); + + //return inscribed; + return false; } /// @@ -170,256 +162,257 @@ namespace WixToolset /// True if database is updated. public bool InscribeDatabase(string databaseFile, string outputFile, bool tidy) { - // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered - bool foundUnsignedExternals = false; - bool shouldCommit = false; - - FileAttributes attributes = File.GetAttributes(databaseFile); - if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) - { - this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile)); - return shouldCommit; - } - - using (Database database = new Database(databaseFile, OpenDatabase.Transact)) - { - // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content - int codepage = 1252; - - // list of certificates for this database (hash/identifier) - Dictionary certificates = new Dictionary(); - - // Reset the in-memory tables for this new database - Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]); - Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]); - - // If any digital signature records exist that are not of the media type, preserve them - if (database.TableExists("MsiDigitalSignature")) - { - using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) - { - while (true) - { - using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) - { - if (null == digitalSignatureRecord) - { - break; - } - - Row digitalSignatureRow = null; - digitalSignatureRow = digitalSignatureTable.CreateRow(null); - - string table = digitalSignatureRecord.GetString(0); - string signObject = digitalSignatureRecord.GetString(1); - - digitalSignatureRow[0] = table; - digitalSignatureRow[1] = signObject; - digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); - - if (false == digitalSignatureRecord.IsNull(3)) - { - // Export to a file, because the MSI API's require us to provide a file path on disk - string hashPath = Path.Combine(this.TempFilesLocation, "MsiDigitalSignature"); - string hashFileName = string.Concat(table, ".", signObject, ".bin"); - - Directory.CreateDirectory(hashPath); - hashPath = Path.Combine(hashPath, hashFileName); - - using (FileStream fs = File.Create(hashPath)) - { - int bytesRead; - byte[] buffer = new byte[1024 * 4]; - - while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - - digitalSignatureRow[3] = hashFileName; - } - } - } - } - } - - // If any digital certificates exist, extract and preserve them - if (database.TableExists("MsiDigitalCertificate")) - { - using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) - { - while (true) - { - using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) - { - if (null == digitalCertificateRecord) - { - break; - } - - string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate - - // Export to a file, because the MSI API's require us to provide a file path on disk - string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); - Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); - - using (FileStream fs = File.Create(certPath)) - { - int bytesRead; - byte[] buffer = new byte[1024 * 4]; - - while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - - // Add it to our "add to MsiDigitalCertificate" table dictionary - Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); - digitalCertificateRow[0] = certificateId; - - // Now set the file path on disk where this binary stream will be picked up at import time - digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); - - // Load the cert to get it's thumbprint - X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); - X509Certificate2 cert2 = new X509Certificate2(cert); - - certificates.Add(cert2.Thumbprint, certificateId); - } - } - } - } - - using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) - { - while (true) - { - using (Record mediaRecord = mediaView.Fetch()) - { - if (null == mediaRecord) - { - break; - } - - X509Certificate2 cert2 = null; - Row digitalSignatureRow = null; - - string cabName = mediaRecord.GetString(4); // get the name of the cab - // If there is no cabinet or it's an internal cab, skip it. - if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) - { - continue; - } - - string cabId = mediaRecord.GetString(1); // get the ID of the cab - string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName); - - // If the cabs aren't there, throw an error but continue to catch the other errors - if (!File.Exists(cabPath)) - { - this.OnMessage(WixErrors.WixFileNotFound(cabPath)); - continue; - } - - try - { - // Get the certificate from the cab - X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); - cert2 = new X509Certificate2(signedFileCert); - } - catch (System.Security.Cryptography.CryptographicException e) - { - uint HResult = unchecked((uint)Marshal.GetHRForException(e)); - - // If the file has no cert, continue, but flag that we found at least one so we can later give a warning - if (0x80092009 == HResult) // CRYPT_E_NO_MATCH - { - foundUnsignedExternals = true; - continue; - } - - // todo: exactly which HRESULT corresponds to this issue? - // If it's one of these exact platforms, warn the user that it may be due to their OS. - if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 - (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP - { - this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); - } - else // otherwise, generic error - { - this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); - } - } - - // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added - if (!certificates.ContainsKey(cert2.Thumbprint)) - { - // generate a stable identifier - string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); - - // Add it to our "add to MsiDigitalCertificate" table dictionary - Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); - digitalCertificateRow[0] = certificateGeneratedId; - - // Export to a file, because the MSI API's require us to provide a file path on disk - string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); - Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); - File.Delete(certPath); - - using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) - { - writer.Write(cert2.RawData); - writer.Close(); - } - - // Now set the file path on disk where this binary stream will be picked up at import time - digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); - - certificates.Add(cert2.Thumbprint, certificateGeneratedId); - } - - digitalSignatureRow = digitalSignatureTable.CreateRow(null); - - digitalSignatureRow[0] = "Media"; - digitalSignatureRow[1] = cabId; - digitalSignatureRow[2] = certificates[cert2.Thumbprint]; - } - } - } - - if (digitalCertificateTable.Rows.Count > 0) - { - database.ImportTable(codepage, digitalCertificateTable, this.TempFilesLocation, true); - shouldCommit = true; - } - - if (digitalSignatureTable.Rows.Count > 0) - { - database.ImportTable(codepage, digitalSignatureTable, this.TempFilesLocation, true); - shouldCommit = true; - } - - // TODO: if we created the table(s), then we should add the _Validation records for them. - - certificates = null; - - // If we did find external cabs but none of them were signed, give a warning - if (foundUnsignedExternals) - { - this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile)); - } - - if (shouldCommit) - { - database.Commit(); - } - } - - return shouldCommit; + //// Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered + //bool foundUnsignedExternals = false; + //bool shouldCommit = false; + + //FileAttributes attributes = File.GetAttributes(databaseFile); + //if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) + //{ + // this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile)); + // return shouldCommit; + //} + + //using (Database database = new Database(databaseFile, OpenDatabase.Transact)) + //{ + // // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content + // int codepage = 1252; + + // // list of certificates for this database (hash/identifier) + // Dictionary certificates = new Dictionary(); + + // // Reset the in-memory tables for this new database + // Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]); + // Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]); + + // // If any digital signature records exist that are not of the media type, preserve them + // if (database.TableExists("MsiDigitalSignature")) + // { + // using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) + // { + // while (true) + // { + // using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) + // { + // if (null == digitalSignatureRecord) + // { + // break; + // } + + // Row digitalSignatureRow = null; + // digitalSignatureRow = digitalSignatureTable.CreateRow(null); + + // string table = digitalSignatureRecord.GetString(0); + // string signObject = digitalSignatureRecord.GetString(1); + + // digitalSignatureRow[0] = table; + // digitalSignatureRow[1] = signObject; + // digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); + + // if (false == digitalSignatureRecord.IsNull(3)) + // { + // // Export to a file, because the MSI API's require us to provide a file path on disk + // string hashPath = Path.Combine(this.TempFilesLocation, "MsiDigitalSignature"); + // string hashFileName = string.Concat(table, ".", signObject, ".bin"); + + // Directory.CreateDirectory(hashPath); + // hashPath = Path.Combine(hashPath, hashFileName); + + // using (FileStream fs = File.Create(hashPath)) + // { + // int bytesRead; + // byte[] buffer = new byte[1024 * 4]; + + // while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) + // { + // fs.Write(buffer, 0, bytesRead); + // } + // } + + // digitalSignatureRow[3] = hashFileName; + // } + // } + // } + // } + // } + + // // If any digital certificates exist, extract and preserve them + // if (database.TableExists("MsiDigitalCertificate")) + // { + // using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) + // { + // while (true) + // { + // using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) + // { + // if (null == digitalCertificateRecord) + // { + // break; + // } + + // string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate + + // // Export to a file, because the MSI API's require us to provide a file path on disk + // string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); + // Directory.CreateDirectory(certPath); + // certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); + + // using (FileStream fs = File.Create(certPath)) + // { + // int bytesRead; + // byte[] buffer = new byte[1024 * 4]; + + // while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) + // { + // fs.Write(buffer, 0, bytesRead); + // } + // } + + // // Add it to our "add to MsiDigitalCertificate" table dictionary + // Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); + // digitalCertificateRow[0] = certificateId; + + // // Now set the file path on disk where this binary stream will be picked up at import time + // digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); + + // // Load the cert to get it's thumbprint + // X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); + // X509Certificate2 cert2 = new X509Certificate2(cert); + + // certificates.Add(cert2.Thumbprint, certificateId); + // } + // } + // } + // } + + // using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) + // { + // while (true) + // { + // using (Record mediaRecord = mediaView.Fetch()) + // { + // if (null == mediaRecord) + // { + // break; + // } + + // X509Certificate2 cert2 = null; + // Row digitalSignatureRow = null; + + // string cabName = mediaRecord.GetString(4); // get the name of the cab + // // If there is no cabinet or it's an internal cab, skip it. + // if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) + // { + // continue; + // } + + // string cabId = mediaRecord.GetString(1); // get the ID of the cab + // string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName); + + // // If the cabs aren't there, throw an error but continue to catch the other errors + // if (!File.Exists(cabPath)) + // { + // this.OnMessage(WixErrors.WixFileNotFound(cabPath)); + // continue; + // } + + // try + // { + // // Get the certificate from the cab + // X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); + // cert2 = new X509Certificate2(signedFileCert); + // } + // catch (System.Security.Cryptography.CryptographicException e) + // { + // uint HResult = unchecked((uint)Marshal.GetHRForException(e)); + + // // If the file has no cert, continue, but flag that we found at least one so we can later give a warning + // if (0x80092009 == HResult) // CRYPT_E_NO_MATCH + // { + // foundUnsignedExternals = true; + // continue; + // } + + // // todo: exactly which HRESULT corresponds to this issue? + // // If it's one of these exact platforms, warn the user that it may be due to their OS. + // if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 + // (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP + // { + // this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); + // } + // else // otherwise, generic error + // { + // this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); + // } + // } + + // // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added + // if (!certificates.ContainsKey(cert2.Thumbprint)) + // { + // // generate a stable identifier + // string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); + + // // Add it to our "add to MsiDigitalCertificate" table dictionary + // Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); + // digitalCertificateRow[0] = certificateGeneratedId; + + // // Export to a file, because the MSI API's require us to provide a file path on disk + // string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); + // Directory.CreateDirectory(certPath); + // certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); + // File.Delete(certPath); + + // using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) + // { + // writer.Write(cert2.RawData); + // writer.Close(); + // } + + // // Now set the file path on disk where this binary stream will be picked up at import time + // digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); + + // certificates.Add(cert2.Thumbprint, certificateGeneratedId); + // } + + // digitalSignatureRow = digitalSignatureTable.CreateRow(null); + + // digitalSignatureRow[0] = "Media"; + // digitalSignatureRow[1] = cabId; + // digitalSignatureRow[2] = certificates[cert2.Thumbprint]; + // } + // } + // } + + // if (digitalCertificateTable.Rows.Count > 0) + // { + // database.ImportTable(codepage, digitalCertificateTable, this.TempFilesLocation, true); + // shouldCommit = true; + // } + + // if (digitalSignatureTable.Rows.Count > 0) + // { + // database.ImportTable(codepage, digitalSignatureTable, this.TempFilesLocation, true); + // shouldCommit = true; + // } + + // // TODO: if we created the table(s), then we should add the _Validation records for them. + + // certificates = null; + + // // If we did find external cabs but none of them were signed, give a warning + // if (foundUnsignedExternals) + // { + // this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile)); + // } + + // if (shouldCommit) + // { + // database.Commit(); + // } + //} + + //return shouldCommit; + return false; } /// diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs index 66a8c32d..092d81dc 100644 --- a/src/WixToolset.Core/Librarian.cs +++ b/src/WixToolset.Core/Librarian.cs @@ -1,10 +1,11 @@ // 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 +namespace WixToolset.Core { using System; using System.Collections.Generic; using System.Linq; + using WixToolset.Core.Bind; using WixToolset.Data; using WixToolset.Link; @@ -13,20 +14,41 @@ namespace WixToolset /// public sealed class Librarian { + public Librarian(LibraryContext context) + { + this.Context = context; + } + + private LibraryContext Context { get; } + /// /// Create a library by combining several intermediates (objects). /// /// The sections to combine into a library. /// Returns the new library. - public Library Combine(IEnumerable
sections, IEnumerable localizations, ILibraryBinaryFileResolver resolver) + public Library Combine() { - var localizationsByCulture = CollateLocalizations(localizations); + foreach (var extension in this.Context.Extensions) + { + extension.PreCombine(this.Context); + } + + var fileResolver = new FileResolver(this.Context.BindPaths, this.Context.Extensions); - var embedFilePaths = ResolveFilePathsToEmbed(sections, resolver); + var localizationsByCulture = CollateLocalizations(this.Context.Localizations); - var library = new Library(sections, localizationsByCulture, embedFilePaths); + var embedFilePaths = ResolveFilePathsToEmbed(this.Context.Sections, fileResolver); - return this.Validate(library); + var library = new Library(this.Context.Sections, localizationsByCulture, embedFilePaths); + + this.Validate(library); + + foreach (var extension in this.Context.Extensions) + { + extension.PostCombine(library); + } + + return library; } /// @@ -70,12 +92,12 @@ namespace WixToolset return localizationsByCulture; } - private static List ResolveFilePathsToEmbed(IEnumerable
sections, ILibraryBinaryFileResolver resolver) + private List ResolveFilePathsToEmbed(IEnumerable
sections, FileResolver fileResolver) { var embedFilePaths = new List(); // Resolve paths to files that are to be embedded in the library. - if (null != resolver) + if (this.Context.BindFiles) { foreach (Table table in sections.SelectMany(s => s.Tables)) { @@ -85,7 +107,10 @@ namespace WixToolset { if (null != objectField.Data) { - string file = resolver.Resolve(row.SourceLineNumbers, table.Name, (string)objectField.Data); + string resolvedPath = this.Context.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, (string)objectField.Data, false); + + string file = fileResolver.Resolve(row.SourceLineNumbers, table.Name, resolvedPath); + if (!String.IsNullOrEmpty(file)) { // File was successfully resolved so track the embedded index as the embedded file index. diff --git a/src/WixToolset.Core/LibraryContext.cs b/src/WixToolset.Core/LibraryContext.cs new file mode 100644 index 00000000..36e38739 --- /dev/null +++ b/src/WixToolset.Core/LibraryContext.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + + public class LibraryContext : ILibraryContext + { + public bool BindFiles { get; set; } + + public IEnumerable BindPaths { get; set; } + + public IEnumerable Extensions { get; set; } + + public IEnumerable Localizations { get; set; } + + public IEnumerable
Sections { get; set; } + + public IBindVariableResolver WixVariableResolver { get; set; } + } +} diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 1e5b6e96..c1c9f848 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -78,7 +78,7 @@ namespace WixToolset /// Gets or sets the Wix variable resolver. ///
/// The Wix variable resolver. - public WixVariableResolver WixVariableResolver { get; set; } + internal IBindVariableResolver WixVariableResolver { get; set; } /// /// Adds an extension. diff --git a/src/WixToolset.Core/Localizer.cs b/src/WixToolset.Core/Localizer.cs index 63ead24a..72d0955b 100644 --- a/src/WixToolset.Core/Localizer.cs +++ b/src/WixToolset.Core/Localizer.cs @@ -8,11 +8,12 @@ namespace WixToolset using WixToolset.Data; using WixToolset.Data.Rows; using WixToolset.Core.Native; + using WixToolset.Extensibility; /// /// Parses localization files and localizes database values. /// - public sealed class Localizer + public sealed class Localizer : ILocalizer { public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; private static string XmlElementName = "WixLocalization"; @@ -55,7 +56,28 @@ namespace WixToolset /// Gets the codepage. /// /// The codepage. - public int Codepage { get; private set; } + public int Codepage { get; } + + /// + /// Get a localized data value. + /// + /// The name of the localization variable. + /// The localized data value or null if it wasn't found. + public string GetLocalizedValue(string id) + { + return this.variables.TryGetValue(id, out var wixVariableRow) ? wixVariableRow.Value : null; + } + + /// + /// Get a localized control. + /// + /// The optional id of the control's dialog. + /// The id of the control. + /// The localized control or null if it wasn't found. + public LocalizedControl GetLocalizedControl(string dialog, string control) + { + return this.localizedControls.TryGetValue(LocalizedControl.GetKey(dialog, control), out var localizedControl) ? localizedControl : null; + } /// /// Loads a localization file from a path on disk. @@ -96,28 +118,6 @@ namespace WixToolset return localization; } - /// - /// Get a localized data value. - /// - /// The name of the localization variable. - /// The localized data value or null if it wasn't found. - public string GetLocalizedValue(string id) - { - return this.variables.TryGetValue(id, out var wixVariableRow) ? wixVariableRow.Value : null; - } - - /// - /// Get a localized control. - /// - /// The optional id of the control's dialog. - /// The id of the control. - /// The localized control or null if it wasn't found. - public LocalizedControl GetLocalizedControl(string dialog, string control) - { - LocalizedControl localizedControl; - return this.localizedControls.TryGetValue(LocalizedControl.GetKey(dialog, control), out localizedControl) ? localizedControl : null; - } - /// /// Adds a WixVariableRow to a dictionary while performing the expected override checks. /// @@ -125,8 +125,7 @@ namespace WixToolset /// Row to add to the variables dictionary. private static void AddWixVariable(IDictionary variables, WixVariableRow wixVariableRow) { - WixVariableRow existingWixVariableRow; - if (!variables.TryGetValue(wixVariableRow.Id, out existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) + if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) { variables[wixVariableRow.Id] = wixVariableRow; } diff --git a/src/WixToolset.Core/MergeMod/NativeMethods.cs b/src/WixToolset.Core/MergeMod/NativeMethods.cs deleted file mode 100644 index daf259b4..00000000 --- a/src/WixToolset.Core/MergeMod/NativeMethods.cs +++ /dev/null @@ -1,508 +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. -#if false -namespace WixToolset.MergeMod -{ - using System; - using System.Collections; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - /// - /// Errors returned by merge operations. - /// - [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")] - internal enum MsmErrorType - { - /// - /// A request was made to open a module with a language not supported by the module. - /// No more general language is supported by the module. - /// Adds msmErrorLanguageUnsupported to the Type property and the requested language - /// to the Language Property (Error Object). All Error object properties are empty. - /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). - /// - msmErrorLanguageUnsupported = 1, - - /// - /// A request was made to open a module with a supported language but the module has - /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property - /// and the applied transform's language to the Language Property of the Error object. - /// This may not be the requested language if a more general language was used. - /// All other properties of the Error object are empty. The OpenModule function - /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). - /// - msmErrorLanguageFailed = 2, - - /// - /// The module cannot be merged because it excludes, or is excluded by, another module - /// in the database. Adds msmErrorExclusion to the Type property of the Error object. - /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the - /// excluded module's row in the ModuleExclusion table. If an existing module excludes - /// the module being merged, the excluded module's ModuleSignature information is added - /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys - /// contains the excluded module's ModuleSignature information. All other properties - /// are empty (or -1). - /// - msmErrorExclusion = 3, - - /// - /// Merge conflict during merge. The value of the Type property is set to - /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain - /// the table name and primary keys of the conflicting row in the database. The - /// ModuleTable property and ModuleKeys property contain the table name and primary keys - /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be - /// null if the row does not exist in the database. For example, if the conflict is in a - /// generated FeatureComponents table entry. On Windows Installer version 2.0, when - /// merging a configurable merge module, configuration may cause these properties to - /// refer to rows that do not exist in the module. - /// - msmErrorTableMerge = 4, - - /// - /// There was a problem resequencing a sequence table to contain the necessary merged - /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable - /// and DatabaseKeys properties contain the sequence table name and primary keys - /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties - /// contain the sequence table name and primary key (action name) of the conflicting row. - /// On Windows Installer version 2.0, when merging a configurable merge module, - /// configuration may cause these properties to refer to rows that do not exist in the module. - /// - msmErrorResequenceMerge = 5, - - /// - /// Not used. - /// - msmErrorFileCreate = 6, - - /// - /// There was a problem creating a directory to extract a file to disk. The Path property - /// contains the directory that could not be created. All other properties are empty or -1. - /// Not available with Windows Installer version 1.0. - /// - msmErrorDirCreate = 7, - - /// - /// A feature name is required to complete the merge, but no feature name was provided. - /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys - /// contain the table name and primary keys of the conflicting row. The ModuleTable and - /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged. - /// On Windows Installer version 2.0, when merging a configurable merge module, configuration - /// may cause these properties to refer to rows that do not exist in the module. - /// If the failure is in a generated FeatureComponents table, the DatabaseTable and - /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to - /// the row in the Component table causing the failure. - /// - msmErrorFeatureRequired = 8, - - /// - /// Available with Window Installer version 2.0. Substitution of a Null value into a - /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and - /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row - /// into the ModuleTable property and ModuleKeys property. All other properties of the Error - /// object are set to an empty string or -1. This error causes the immediate failure of the - /// merge and the MergeEx function to return E_FAIL. - /// - msmErrorBadNullSubstitution = 9, - - /// - /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer - /// Format Type into a Binary Type data column. This type of error returns - /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the - /// keys from the ModuleSubstitution table for this row into the ModuleTable property. - /// All other properties of the Error object are set to an empty string or -1. This error - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. - /// - msmErrorBadSubstitutionType = 10, - - /// - /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table - /// references a configuration item not defined in the ModuleConfiguration table. - /// This type of error returns msmErrorMissingConfigItem in the Type property and enters - /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into - /// the ModuleTable property. All other properties of the Error object are set to an empty - /// string or -1. This error causes the immediate failure of the merge and the MergeEx - /// function to return E_FAIL. - /// - msmErrorMissingConfigItem = 11, - - /// - /// Available with Window Installer version 2.0. The authoring tool has returned a Null - /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this - /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution" - /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property. - /// All other properties of the Error object are set to an empty string or -1. This error - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. - /// - msmErrorBadNullResponse = 12, - - /// - /// Available with Window Installer version 2.0. The authoring tool returned a failure code - /// (not S_OK or S_FALSE) when asked for data. An error of this type will return - /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution" - /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property. - /// All other properties of the Error object are set to an empty string or -1. This error - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. - /// - msmErrorDataRequestFailed = 13, - - /// - /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was - /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of - /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of - /// the error object are set to an empty string or -1. This error causes the immediate failure - /// of the merge and causes the Merge function or MergeEx function to return E_FAIL. - /// - msmErrorPlatformMismatch = 14, - } - - /// - /// IMsmMerge2 interface. - /// - [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")] - internal interface IMsmMerge2 - { - /// - /// The OpenDatabase method of the Merge object opens a Windows Installer installation - /// database, located at a specified path, that is to be merged with a module. - /// - /// Path to the database being opened. - void OpenDatabase(string path); - - /// - /// The OpenModule method of the Merge object opens a Windows Installer merge module - /// in read-only mode. A module must be opened before it can be merged with an installation database. - /// - /// Fully qualified file name pointing to a merge module. - /// A valid language identifier (LANGID). - void OpenModule(string fileName, short language); - - /// - /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database. - /// - /// true if changes should be saved, false otherwise. - void CloseDatabase(bool commit); - - /// - /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module. - /// - void CloseModule(); - - /// - /// The OpenLog method of the Merge object opens a log file that receives progress and error messages. - /// If the log file already exists, the installer appends new messages. If the log file does not exist, - /// the installer creates a log file. - /// - /// Fully qualified filename pointing to a file to open or create. - void OpenLog(string fileName); - - /// - /// The CloseLog method of the Merge object closes the current log file. - /// - void CloseLog(); - - /// - /// The Log method of the Merge object writes a text string to the currently open log file. - /// - /// The text string to display. - void Log(string message); - - /// - /// Gets the errors from the last merge operation. - /// - /// The errors from the last merge operation. - IMsmErrors Errors - { - get; - } - - /// - /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. - /// - /// A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. - object Dependencies - { - get; - } - - /// - /// The Merge method of the Merge object executes a merge of the current database and current - /// module. The merge attaches the components in the module to the feature identified by Feature. - /// The root of the module's directory tree is redirected to the location given by RedirectDir. - /// - /// The name of a feature in the database. - /// The key of an entry in the Directory table of the database. - /// This parameter may be NULL or an empty string. - void Merge(string feature, string redirectDir); - - /// - /// The Connect method of the Merge object connects a module to an additional feature. - /// The module must have already been merged into the database or will be merged into the database. - /// The feature must exist before calling this function. - /// - /// The name of a feature already existing in the database. - void Connect(string feature); - - /// - /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and - /// saves it as the specified file. The installer creates this file if it does not already exist - /// and overwritten if it does exist. - /// - /// The fully qualified destination file. - void ExtractCAB(string fileName); - - /// - /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module - /// and then writes those files to the destination directory. - /// - /// The fully qualified destination directory. - void ExtractFiles(string path); - - /// - /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it - /// takes an extra argument. The Merge method executes a merge of the current database and - /// current module. The merge attaches the components in the module to the feature identified - /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir. - /// - /// The name of a feature in the database. - /// The key of an entry in the Directory table of the database. This parameter may - /// be NULL or an empty string. - /// The pConfiguration argument is an interface implemented by the client. The argument may - /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration - /// functionality, but does not obligate the client to provide configuration data for any specific configurable item. - void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration); - - /// - /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and - /// then writes those files to the destination directory. - /// - /// The fully qualified destination directory. - /// Set to specify using long file names for path segments and final file names. - /// This is a list of fully-qualified paths for the files that were successfully extracted. - /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. - void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths); - - /// - /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. - /// - /// A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. - /// Semantically, each interface in the enumerator represents an item that can be configured by the module consumer. - /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum(). - /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics. - object ConfigurableItems - { - get; - } - - /// - /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to - /// a source image on disk after a merge, taking into account changes to the module that might have been made - /// during module configuration. The list of files to be extracted is taken from the file table of the module - /// during the merge process. The list of files consists of every file successfully copied from the file table - /// of the module to the target database. File table entries that were not copied due to primary key conflicts - /// with existing rows in the database are not a part of this list. At image creation time, the directory for - /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is - /// the root of the source image for the install. fLongFileNames determines whether or not long file names are - /// used for both path segments and final file names. The function fails if no database is open, no module is - /// open, or no merge has been performed. - /// - /// The path of the root of the source image for the install. - /// Determines whether or not long file names are used for both path segments and final file names. - /// This is a list of fully-qualified paths for the files that were successfully extracted. - /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. - void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths); - - /// - /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function - /// returns the primary keys in the File table of the currently open module. The primary keys are returned - /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles. - /// - IMsmStrings ModuleFiles - { - get; - } - } - - /// - /// Collection of merge errors. - /// - [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")] - internal interface IMsmErrors - { - /// - /// Gets the IMsmError at the specified index. - /// - /// The one-based index of the IMsmError to get. - IMsmError this[int index] - { - get; - } - - /// - /// Gets the count of IMsmErrors in this collection. - /// - /// The count of IMsmErrors in this collection. - int Count - { - get; - } - } - - /// - /// A merge error. - /// - [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")] - internal interface IMsmError - { - /// - /// Gets the type of merge error. - /// - /// The type of merge error. - MsmErrorType Type - { - get; - } - - /// - /// Gets the path information from the merge error. - /// - /// The path information from the merge error. - string Path - { - get; - } - - /// - /// Gets the language information from the merge error. - /// - /// The language information from the merge error. - short Language - { - get; - } - - /// - /// Gets the database table from the merge error. - /// - /// The database table from the merge error. - string DatabaseTable - { - get; - } - - /// - /// Gets the collection of database keys from the merge error. - /// - /// The collection of database keys from the merge error. - IMsmStrings DatabaseKeys - { - get; - } - - /// - /// Gets the module table from the merge error. - /// - /// The module table from the merge error. - string ModuleTable - { - get; - } - - /// - /// Gets the collection of module keys from the merge error. - /// - /// The collection of module keys from the merge error. - IMsmStrings ModuleKeys - { - get; - } - } - - /// - /// A collection of strings. - /// - [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")] - internal interface IMsmStrings - { - /// - /// Gets the string at the specified index. - /// - /// The one-based index of the string to get. - string this[int index] - { - get; - } - - /// - /// Gets the count of strings in this collection. - /// - /// The count of strings in this collection. - int Count - { - get; - } - } - - /// - /// Callback for configurable merge modules. - /// - [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] - internal interface IMsmConfigureModule - { - /// - /// Callback to retrieve text data for configurable merge modules. - /// - /// Name of the data to be retrieved. - /// The data corresponding to the name. - /// The error code (HRESULT). - [PreserveSig] - int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData); - - /// - /// Callback to retrieve integer data for configurable merge modules. - /// - /// Name of the data to be retrieved. - /// The data corresponding to the name. - /// The error code (HRESULT). - [PreserveSig] - int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData); - } - - /// - /// Merge merge modules into an MSI file. - /// - [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")] - internal class MsmMerge2 - { - } - - /// - /// Defines the standard COM IClassFactory interface. - /// - [ComImport, Guid("00000001-0000-0000-C000-000000000046")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IClassFactory - { - [return:MarshalAs(UnmanagedType.IUnknown)] - object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); - } - - /// - /// Contains native methods for merge operations. - /// - internal class NativeMethods - { - [DllImport("mergemod.dll", EntryPoint="DllGetClassObject", PreserveSig=false)] - [return: MarshalAs(UnmanagedType.IUnknown)] - private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); - - /// - /// Load the merge object directly from a local mergemod.dll without going through COM registration. - /// - /// Merge interface. - internal static IMsmMerge2 GetMsmMerge() - { - IClassFactory classFactory = (IClassFactory) MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID); - return (IMsmMerge2) classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID); - } - } -} -#endif \ No newline at end of file diff --git a/src/WixToolset.Core/Msi/Database.cs b/src/WixToolset.Core/Msi/Database.cs deleted file mode 100644 index 801ebdde..00000000 --- a/src/WixToolset.Core/Msi/Database.cs +++ /dev/null @@ -1,303 +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.Msi -{ - using System; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Text; - using System.Threading; - using WixToolset.Data; - using WixToolset.Core.Native; - - /// - /// Wrapper class for managing MSI API database handles. - /// - internal sealed class Database : MsiHandle - { - private const int STG_E_LOCKVIOLATION = unchecked((int)0x80030021); - - /// - /// Constructor that opens an MSI database. - /// - /// Path to the database to be opened. - /// Persist mode to use when opening the database. - public Database(string path, OpenDatabase type) - { - uint handle = 0; - int error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out handle); - if (0 != error) - { - throw new MsiException(error); - } - this.Handle = handle; - } - - public void ApplyTransform(string transformFile) - { - // get the curret validation bits - TransformErrorConditions conditions = TransformErrorConditions.None; - using (SummaryInformation summaryInfo = new SummaryInformation(transformFile)) - { - string value = summaryInfo.GetProperty((int)SummaryInformation.Transform.ValidationFlags); - try - { - int validationFlags = Int32.Parse(value, CultureInfo.InvariantCulture); - conditions = (TransformErrorConditions)(validationFlags & 0xffff); - } - catch (FormatException) - { - // fallback to default of None - } - } - - this.ApplyTransform(transformFile, conditions); - } - - /// - /// Applies a transform to this database. - /// - /// Path to the transform file being applied. - /// Specifies the error conditions that are to be suppressed. - public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions) - { - int error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions); - if (0 != error) - { - throw new MsiException(error); - } - } - - /// - /// Commits changes made to the database. - /// - public void Commit() - { - // Retry this call 3 times to deal with an MSI internal locking problem. - const int retryWait = 300; - const int retryLimit = 3; - int error = 0; - - for (int i = 1; i <= retryLimit; ++i) - { - error = MsiInterop.MsiDatabaseCommit(this.Handle); - - if (0 == error) - { - return; - } - else - { - MsiException exception = new MsiException(error); - - // We need to see if the error code is contained in any of the strings in ErrorInfo. - // Join the array together and search for the error code to cover the string array. - if (!String.Join(", ", exception.ErrorInfo).Contains(STG_E_LOCKVIOLATION.ToString())) - { - break; - } - - Console.Error.WriteLine(String.Format("Failed to create the database. Info: {0}. Retrying ({1} of {2})", String.Join(", ", exception.ErrorInfo), i, retryLimit)); - Thread.Sleep(retryWait); - } - } - - throw new MsiException(error); - } - - /// - /// Creates and populates the summary information stream of an existing transform file. - /// - /// Required database that does not include the changes. - /// The name of the generated transform file. - /// Required error conditions that should be suppressed when the transform is applied. - /// Required when the transform is applied to a database; - /// shows which properties should be validated to verify that this transform can be applied to the database. - public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations) - { - int error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations); - if (0 != error) - { - throw new MsiException(error); - } - } - - /// - /// Imports an installer text archive table (idt file) into an open database. - /// - /// Specifies the path to the file to import. - /// Attempted to import an IDT file with an invalid format or unsupported data. - /// Another error occured while importing the IDT file. - public void Import(string idtPath) - { - string folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath)); - string fileName = Path.GetFileName(idtPath); - - int error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName); - if (1627 == error) // ERROR_FUNCTION_FAILED - { - throw new WixInvalidIdtException(idtPath); - } - else if (0 != error) - { - throw new MsiException(error); - } - } - - /// - /// Exports an installer table from an open database to a text archive file (idt file). - /// - /// Specifies the name of the table to export. - /// Specifies the name of the folder that contains archive files. If null or empty string, uses current directory. - /// Specifies the name of the exported table archive file. - public void Export(string tableName, string folderPath, string fileName) - { - if (null == folderPath || 0 == folderPath.Length) - { - folderPath = System.Environment.CurrentDirectory; - } - - int error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName); - if (0 != error) - { - throw new MsiException(error); - } - } - - /// - /// Creates a transform that, when applied to the reference database, results in this database. - /// - /// Required database that does not include the changes. - /// The name of the generated transform file. This is optional. - /// true if a transform is generated; false if a transform is not generated because - /// there are no differences between the two databases. - public bool GenerateTransform(Database referenceDatabase, string transformFile) - { - int error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0); - if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found - { - throw new MsiException(error); - } - - return (0xE8 != error); - } - - /// - /// Merges two databases together. - /// - /// The database to merge into the base database. - /// The name of the table to receive merge conflict information. - public void Merge(Database mergeDatabase, string tableName) - { - int error = MsiInterop.MsiDatabaseMerge(this.Handle, mergeDatabase.Handle, tableName); - if (0 != error) - { - throw new MsiException(error); - } - } - - /// - /// Prepares a database query and creates a View object. - /// - /// Specifies a SQL query string for querying the database. - /// A view object is returned if the query was successful. - public View OpenView(string query) - { - return new View(this, query); - } - - /// - /// Prepares and executes a database query and creates a View object. - /// - /// Specifies a SQL query string for querying the database. - /// A view object is returned if the query was successful. - public View OpenExecuteView(string query) - { - View view = new View(this, query); - - view.Execute(); - return view; - } - - /// - /// Verifies the existence or absence of a table. - /// - /// Table name to to verify the existence of. - /// Returns true if the table exists, false if it does not. - public bool TableExists(string tableName) - { - int result = MsiInterop.MsiDatabaseIsTablePersistent(this.Handle, tableName); - return MsiInterop.MSICONDITIONTRUE == result; - } - - /// - /// Returns a Record containing the names of all the primary - /// key columns for a specified table. - /// - /// Specifies the name of the table from which to obtain - /// primary key names. - /// Returns a Record containing the names of all the - /// primary key columns for a specified table. - public Record PrimaryKeys(string tableName) - { - uint recordHandle; - int error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out recordHandle); - if (0 != error) - { - throw new MsiException(error); - } - - return new Record(recordHandle); - } - - /// - /// Imports a table into the database. - /// - /// Codepage of the database to import table to. - /// Table to import into database. - /// The base directory where intermediate files are created. - /// Whether to keep columns added in a transform. - public void ImportTable(int codepage, Table table, string baseDirectory, bool keepAddedColumns) - { - // write out the table to an IDT file - string idtPath = Path.Combine(baseDirectory, String.Concat(table.Name, ".idt")); - Encoding encoding; - - // If UTF8 encoding, use the UTF8-specific constructor to avoid writing - // the byte order mark at the beginning of the file - if (Encoding.UTF8.CodePage == codepage) - { - encoding = new UTF8Encoding(false, true); - } - else - { - if (0 == codepage) - { - codepage = Encoding.ASCII.CodePage; - } - - encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - } - - using (StreamWriter idtWriter = new StreamWriter(idtPath, false, encoding)) - { - table.ToIdtDefinition(idtWriter, keepAddedColumns); - } - - // try to import the table into the MSI - try - { - this.Import(idtPath); - } - catch (WixInvalidIdtException) - { - table.ValidateRows(); - - // If ValidateRows finds anything it doesn't like, it throws. Otherwise, we'll - // throw WixInvalidIdtException here which is caught in light and turns off tidy. - throw new WixInvalidIdtException(idtPath, table.Name); - } - } - } -} diff --git a/src/WixToolset.Core/Msi/Installer.cs b/src/WixToolset.Core/Msi/Installer.cs deleted file mode 100644 index 3beb26f4..00000000 --- a/src/WixToolset.Core/Msi/Installer.cs +++ /dev/null @@ -1,484 +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.Msi -{ - using System; - using System.Diagnostics; - using System.Text; - using WixToolset.Core.Native; - - /// - /// Windows Installer message types. - /// - [Flags] - internal enum InstallMessage - { - /// - /// Premature termination, possibly fatal out of memory. - /// - FatalExit = 0x00000000, - - /// - /// Formatted error message, [1] is message number in Error table. - /// - Error = 0x01000000, - - /// - /// Formatted warning message, [1] is message number in Error table. - /// - Warning = 0x02000000, - - /// - /// User request message, [1] is message number in Error table. - /// - User = 0x03000000, - - /// - /// Informative message for log, not to be displayed. - /// - Info = 0x04000000, - - /// - /// List of files in use that need to be replaced. - /// - FilesInUse = 0x05000000, - - /// - /// Request to determine a valid source location. - /// - ResolveSource = 0x06000000, - - /// - /// Insufficient disk space message. - /// - OutOfDiskSpace = 0x07000000, - - /// - /// Progress: start of action, [1] action name, [2] description, [3] template for ACTIONDATA messages. - /// - ActionStart = 0x08000000, - - /// - /// Action data. Record fields correspond to the template of ACTIONSTART message. - /// - ActionData = 0x09000000, - - /// - /// Progress bar information. See the description of record fields below. - /// - Progress = 0x0A000000, - - /// - /// To enable the Cancel button set [1] to 2 and [2] to 1. To disable the Cancel button set [1] to 2 and [2] to 0. - /// - CommonData = 0x0B000000, - - /// - /// Sent prior to UI initialization, no string data. - /// - Initilize = 0x0C000000, - - /// - /// Sent after UI termination, no string data. - /// - Terminate = 0x0D000000, - - /// - /// Sent prior to display or authored dialog or wizard. - /// - ShowDialog = 0x0E000000 - } - - /// - /// Windows Installer log modes. - /// - [Flags] - internal enum InstallLogModes - { - /// - /// Premature termination of installation. - /// - FatalExit = (1 << ((int)InstallMessage.FatalExit >> 24)), - - /// - /// The error messages are logged. - /// - Error = (1 << ((int)InstallMessage.Error >> 24)), - - /// - /// The warning messages are logged. - /// - Warning = (1 << ((int)InstallMessage.Warning >> 24)), - - /// - /// The user requests are logged. - /// - User = (1 << ((int)InstallMessage.User >> 24)), - - /// - /// The status messages that are not displayed are logged. - /// - Info = (1 << ((int)InstallMessage.Info >> 24)), - - /// - /// Request to determine a valid source location. - /// - ResolveSource = (1 << ((int)InstallMessage.ResolveSource >> 24)), - - /// - /// The was insufficient disk space. - /// - OutOfDiskSpace = (1 << ((int)InstallMessage.OutOfDiskSpace >> 24)), - - /// - /// The start of new installation actions are logged. - /// - ActionStart = (1 << ((int)InstallMessage.ActionStart >> 24)), - - /// - /// The data record with the installation action is logged. - /// - ActionData = (1 << ((int)InstallMessage.ActionData >> 24)), - - /// - /// The parameters for user-interface initialization are logged. - /// - CommonData = (1 << ((int)InstallMessage.CommonData >> 24)), - - /// - /// Logs the property values at termination. - /// - PropertyDump = (1 << ((int)InstallMessage.Progress >> 24)), - - /// - /// Sends large amounts of information to a log file not generally useful to users. - /// May be used for technical support. - /// - Verbose = (1 << ((int)InstallMessage.Initilize >> 24)), - - /// - /// Sends extra debugging information, such as handle creation information, to the log file. - /// - ExtraDebug = (1 << ((int)InstallMessage.Terminate >> 24)), - - /// - /// Progress bar information. This message includes information on units so far and total number of units. - /// See MsiProcessMessage for an explanation of the message format. - /// This message is only sent to an external user interface and is not logged. - /// - Progress = (1 << ((int)InstallMessage.Progress >> 24)), - - /// - /// If this is not a quiet installation, then the basic UI has been initialized. - /// If this is a full UI installation, the full UI is not yet initialized. - /// This message is only sent to an external user interface and is not logged. - /// - Initialize = (1 << ((int)InstallMessage.Initilize >> 24)), - - /// - /// If a full UI is being used, the full UI has ended. - /// If this is not a quiet installation, the basic UI has not yet ended. - /// This message is only sent to an external user interface and is not logged. - /// - Terminate = (1 << ((int)InstallMessage.Terminate >> 24)), - - /// - /// Sent prior to display of the full UI dialog. - /// This message is only sent to an external user interface and is not logged. - /// - ShowDialog = (1 << ((int)InstallMessage.ShowDialog >> 24)), - - /// - /// Files in use information. When this message is received, a FilesInUse Dialog should be displayed. - /// - FilesInUse = (1 << ((int)InstallMessage.FilesInUse >> 24)) - } - - /// - /// Windows Installer UI levels. - /// - [Flags] - internal enum InstallUILevels - { - /// - /// No change in the UI level. However, if phWnd is not Null, the parent window can change. - /// - NoChange = 0, - - /// - /// The installer chooses an appropriate user interface level. - /// - Default = 1, - - /// - /// Completely silent installation. - /// - None = 2, - - /// - /// Simple progress and error handling. - /// - Basic = 3, - - /// - /// Authored user interface with wizard dialog boxes suppressed. - /// - Reduced = 4, - - /// - /// Authored user interface with wizards, progress, and errors. - /// - Full = 5, - - /// - /// If combined with the Basic value, the installer shows simple progress dialog boxes but - /// does not display a Cancel button on the dialog. This prevents users from canceling the install. - /// Available with Windows Installer version 2.0. - /// - HideCancel = 0x20, - - /// - /// If combined with the Basic value, the installer shows simple progress - /// dialog boxes but does not display any modal dialog boxes or error dialog boxes. - /// - ProgressOnly = 0x40, - - /// - /// If combined with any above value, the installer displays a modal dialog - /// box at the end of a successful installation or if there has been an error. - /// No dialog box is displayed if the user cancels. - /// - EndDialog = 0x80, - - /// - /// If this value is combined with the None value, the installer displays only the dialog - /// boxes used for source resolution. No other dialog boxes are shown. This value has no - /// effect if the UI level is not INSTALLUILEVEL_NONE. It is used with an external user - /// interface designed to handle all of the UI except for source resolution. In this case, - /// the installer handles source resolution. This value is only available with Windows Installer 2.0 and later. - /// - SourceResOnly = 0x100 - } - - /// - /// Represents the Windows Installer, provides wrappers to - /// create the top-level objects and access their methods. - /// - internal sealed class Installer - { - /// - /// Protect the constructor. - /// - private Installer() - { - } - - /// - /// Takes the path to a file and returns a 128-bit hash of that file. - /// - /// Path to file that is to be hashed. - /// The value in this column must be 0. This parameter is reserved for future use. - /// Int array that receives the returned file hash information. - internal static void GetFileHash(string filePath, int options, out int[] hash) - { - MsiInterop.MSIFILEHASHINFO hashInterop = new MsiInterop.MSIFILEHASHINFO(); - hashInterop.FileHashInfoSize = 20; - - int error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop); - if (0 != error) - { - throw new MsiException(error); - } - - Debug.Assert(20 == hashInterop.FileHashInfoSize); - - hash = new int[4]; - hash[0] = hashInterop.Data0; - hash[1] = hashInterop.Data1; - hash[2] = hashInterop.Data2; - hash[3] = hashInterop.Data3; - } - - /// - /// Returns the version string and language string in the format that the installer - /// expects to find them in the database. If you just want version information, set - /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set - /// lpVersionBuf and pcchVersionBuf to zero. - /// - /// Specifies the path to the file. - /// Returns the file version. Set to 0 for language information only. - /// Returns the file language. Set to 0 for version information only. - internal static void GetFileVersion(string filePath, out string version, out string language) - { - int versionLength = 20; - int languageLength = 20; - StringBuilder versionBuffer = new StringBuilder(versionLength); - StringBuilder languageBuffer = new StringBuilder(languageLength); - - int error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); - if (234 == error) - { - versionBuffer.EnsureCapacity(++versionLength); - languageBuffer.EnsureCapacity(++languageLength); - error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); - } - else if (1006 == error) - { - // file has no version or language, so no error - error = 0; - } - - if (0 != error) - { - throw new MsiException(error); - } - - version = versionBuffer.ToString(); - language = languageBuffer.ToString(); - } - - /// - /// Enables an external user-interface handler. - /// - /// Specifies a callback function. - /// Specifies which messages to handle using the external message handler. - /// Pointer to an application context that is passed to the callback function. - /// The return value is the previously set external handler, or null if there was no previously set handler. - internal static InstallUIHandler SetExternalUI(InstallUIHandler installUIHandler, int messageFilter, IntPtr context) - { - return MsiInterop.MsiSetExternalUI(installUIHandler, messageFilter, context); - } - - /// - /// Enables the installer's internal user interface. - /// - /// Specifies the level of complexity of the user interface. - /// Pointer to a window. This window becomes the owner of any user interface created. - /// The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned. - internal static int SetInternalUI(int uiLevel, ref IntPtr hwnd) - { - return MsiInterop.MsiSetInternalUI(uiLevel, ref hwnd); - } - - /// - /// Get the source/target and short/long file names from an MSI Filename column. - /// - /// The Filename value. - /// An array of strings of length 4. The contents are: short target, long target, short source, and long source. - /// - /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings. - /// However, the returned array will always be of length 4. - /// - internal static string[] GetNames(string value) - { - string[] names = new string[4]; - int targetSeparator = value.IndexOf(":", StringComparison.Ordinal); - - // split source and target - string sourceName = null; - string targetName = value; - if (0 <= targetSeparator) - { - sourceName = value.Substring(targetSeparator + 1); - targetName = value.Substring(0, targetSeparator); - } - - // split the source short and long names - string sourceLongName = null; - if (null != sourceName) - { - int sourceLongNameSeparator = sourceName.IndexOf("|", StringComparison.Ordinal); - if (0 <= sourceLongNameSeparator) - { - sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); - sourceName = sourceName.Substring(0, sourceLongNameSeparator); - } - } - - // split the target short and long names - int targetLongNameSeparator = targetName.IndexOf("|", StringComparison.Ordinal); - string targetLongName = null; - if (0 <= targetLongNameSeparator) - { - targetLongName = targetName.Substring(targetLongNameSeparator + 1); - targetName = targetName.Substring(0, targetLongNameSeparator); - } - - // remove the long source name when its identical to the long source name - if (null != sourceName && sourceName == sourceLongName) - { - sourceLongName = null; - } - - // remove the long target name when its identical to the long target name - if (null != targetName && targetName == targetLongName) - { - targetLongName = null; - } - - // remove the source names when they are identical to the target names - if (sourceName == targetName && sourceLongName == targetLongName) - { - sourceName = null; - sourceLongName = null; - } - - // target name(s) - if ("." != targetName) - { - names[0] = targetName; - } - - if (null != targetLongName && "." != targetLongName) - { - names[1] = targetLongName; - } - - // source name(s) - if (null != sourceName) - { - names[2] = sourceName; - } - - if (null != sourceLongName && "." != sourceLongName) - { - names[3] = sourceLongName; - } - - return names; - } - - /// - /// Get a source/target and short/long file name from an MSI Filename column. - /// - /// The Filename value. - /// true to get a source name; false to get a target name - /// true to get a long name; false to get a short name - /// The name. - internal static string GetName(string value, bool source, bool longName) - { - string[] names = GetNames(value); - - if (source) - { - if (longName && null != names[3]) - { - return names[3]; - } - else if (null != names[2]) - { - return names[2]; - } - } - - if (longName && null != names[1]) - { - return names[1]; - } - else - { - return names[0]; - } - } - } -} diff --git a/src/WixToolset.Core/Msi/Interop/MsiInterop.cs b/src/WixToolset.Core/Msi/Interop/MsiInterop.cs deleted file mode 100644 index 054289ee..00000000 --- a/src/WixToolset.Core/Msi/Interop/MsiInterop.cs +++ /dev/null @@ -1,697 +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. -#if false -namespace WixToolset.Msi.Interop -{ - using System; - using System.Text; - using System.Runtime.InteropServices; - using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; - - /// - /// A callback function that the installer calls for progress notification and error messages. - /// - /// Pointer to an application context. - /// This parameter can be used for error checking. - /// Specifies a combination of one message box style, - /// one message box icon type, one default button, and one installation message type. - /// Specifies the message text. - /// -1 for an error, 0 if no action was taken, 1 if OK, 3 to abort. - internal delegate int InstallUIHandler(IntPtr context, uint messageType, [MarshalAs(UnmanagedType.LPWStr)] string message); - - /// - /// Class exposing static functions and structs from MSI API. - /// - internal sealed class MsiInterop - { - // Patching constants - internal const int MsiMaxStreamNameLength = 62; // http://msdn2.microsoft.com/library/aa370551.aspx - - // Component.Attributes - internal const int MsidbComponentAttributesLocalOnly = 0; - internal const int MsidbComponentAttributesSourceOnly = 1; - internal const int MsidbComponentAttributesOptional = 2; - internal const int MsidbComponentAttributesRegistryKeyPath = 4; - internal const int MsidbComponentAttributesSharedDllRefCount = 8; - internal const int MsidbComponentAttributesPermanent = 16; - internal const int MsidbComponentAttributesODBCDataSource = 32; - internal const int MsidbComponentAttributesTransitive = 64; - internal const int MsidbComponentAttributesNeverOverwrite = 128; - internal const int MsidbComponentAttributes64bit = 256; - internal const int MsidbComponentAttributesDisableRegistryReflection = 512; - internal const int MsidbComponentAttributesUninstallOnSupersedence = 1024; - internal const int MsidbComponentAttributesShared = 2048; - - // BBControl.Attributes & Control.Attributes - internal const int MsidbControlAttributesVisible = 0x00000001; - internal const int MsidbControlAttributesEnabled = 0x00000002; - internal const int MsidbControlAttributesSunken = 0x00000004; - internal const int MsidbControlAttributesIndirect = 0x00000008; - internal const int MsidbControlAttributesInteger = 0x00000010; - internal const int MsidbControlAttributesRTLRO = 0x00000020; - internal const int MsidbControlAttributesRightAligned = 0x00000040; - internal const int MsidbControlAttributesLeftScroll = 0x00000080; - internal const int MsidbControlAttributesBiDi = MsidbControlAttributesRTLRO | MsidbControlAttributesRightAligned | MsidbControlAttributesLeftScroll; - - // Text controls - internal const int MsidbControlAttributesTransparent = 0x00010000; - internal const int MsidbControlAttributesNoPrefix = 0x00020000; - internal const int MsidbControlAttributesNoWrap = 0x00040000; - internal const int MsidbControlAttributesFormatSize = 0x00080000; - internal const int MsidbControlAttributesUsersLanguage = 0x00100000; - - // Edit controls - internal const int MsidbControlAttributesMultiline = 0x00010000; - internal const int MsidbControlAttributesPasswordInput = 0x00200000; - - // ProgressBar controls - internal const int MsidbControlAttributesProgress95 = 0x00010000; - - // VolumeSelectCombo and DirectoryCombo controls - internal const int MsidbControlAttributesRemovableVolume = 0x00010000; - internal const int MsidbControlAttributesFixedVolume = 0x00020000; - internal const int MsidbControlAttributesRemoteVolume = 0x00040000; - internal const int MsidbControlAttributesCDROMVolume = 0x00080000; - internal const int MsidbControlAttributesRAMDiskVolume = 0x00100000; - internal const int MsidbControlAttributesFloppyVolume = 0x00200000; - - // VolumeCostList controls - internal const int MsidbControlShowRollbackCost = 0x00400000; - - // ListBox and ComboBox controls - internal const int MsidbControlAttributesSorted = 0x00010000; - internal const int MsidbControlAttributesComboList = 0x00020000; - - // picture button controls - internal const int MsidbControlAttributesImageHandle = 0x00010000; - internal const int MsidbControlAttributesPushLike = 0x00020000; - internal const int MsidbControlAttributesBitmap = 0x00040000; - internal const int MsidbControlAttributesIcon = 0x00080000; - internal const int MsidbControlAttributesFixedSize = 0x00100000; - internal const int MsidbControlAttributesIconSize16 = 0x00200000; - internal const int MsidbControlAttributesIconSize32 = 0x00400000; - internal const int MsidbControlAttributesIconSize48 = 0x00600000; - internal const int MsidbControlAttributesElevationShield = 0x00800000; - - // RadioButton controls - internal const int MsidbControlAttributesHasBorder = 0x01000000; - - // CustomAction.Type - // executable types - internal const int MsidbCustomActionTypeDll = 0x00000001; // Target = entry point name - internal const int MsidbCustomActionTypeExe = 0x00000002; // Target = command line args - internal const int MsidbCustomActionTypeTextData = 0x00000003; // Target = text string to be formatted and set into property - internal const int MsidbCustomActionTypeJScript = 0x00000005; // Target = entry point name; null if none to call - internal const int MsidbCustomActionTypeVBScript = 0x00000006; // Target = entry point name; null if none to call - internal const int MsidbCustomActionTypeInstall = 0x00000007; // Target = property list for nested engine initialization - internal const int MsidbCustomActionTypeSourceBits = 0x00000030; - internal const int MsidbCustomActionTypeTargetBits = 0x00000007; - internal const int MsidbCustomActionTypeReturnBits = 0x000000C0; - internal const int MsidbCustomActionTypeExecuteBits = 0x00000700; - - // source of code - internal const int MsidbCustomActionTypeBinaryData = 0x00000000; // Source = Binary.Name; data stored in stream - internal const int MsidbCustomActionTypeSourceFile = 0x00000010; // Source = File.File; file part of installation - internal const int MsidbCustomActionTypeDirectory = 0x00000020; // Source = Directory.Directory; folder containing existing file - internal const int MsidbCustomActionTypeProperty = 0x00000030; // Source = Property.Property; full path to executable - - // return processing; default is syncronous execution; process return code - internal const int MsidbCustomActionTypeContinue = 0x00000040; // ignore action return status; continue running - internal const int MsidbCustomActionTypeAsync = 0x00000080; // run asynchronously - - // execution scheduling flags; default is execute whenever sequenced - internal const int MsidbCustomActionTypeFirstSequence = 0x00000100; // skip if UI sequence already run - internal const int MsidbCustomActionTypeOncePerProcess = 0x00000200; // skip if UI sequence already run in same process - internal const int MsidbCustomActionTypeClientRepeat = 0x00000300; // run on client only if UI already run on client - internal const int MsidbCustomActionTypeInScript = 0x00000400; // queue for execution within script - internal const int MsidbCustomActionTypeRollback = 0x00000100; // in conjunction with InScript: queue in Rollback script - internal const int MsidbCustomActionTypeCommit = 0x00000200; // in conjunction with InScript: run Commit ops from script on success - - // security context flag; default to impersonate as user; valid only if InScript - internal const int MsidbCustomActionTypeNoImpersonate = 0x00000800; // no impersonation; run in system context - internal const int MsidbCustomActionTypeTSAware = 0x00004000; // impersonate for per-machine installs on TS machines - internal const int MsidbCustomActionType64BitScript = 0x00001000; // script should run in 64bit process - internal const int MsidbCustomActionTypeHideTarget = 0x00002000; // don't record the contents of the Target field in the log file. - - internal const int MsidbCustomActionTypePatchUninstall = 0x00008000; // run on patch uninstall - - // Dialog.Attributes - internal const int MsidbDialogAttributesVisible = 0x00000001; - internal const int MsidbDialogAttributesModal = 0x00000002; - internal const int MsidbDialogAttributesMinimize = 0x00000004; - internal const int MsidbDialogAttributesSysModal = 0x00000008; - internal const int MsidbDialogAttributesKeepModeless = 0x00000010; - internal const int MsidbDialogAttributesTrackDiskSpace = 0x00000020; - internal const int MsidbDialogAttributesUseCustomPalette = 0x00000040; - internal const int MsidbDialogAttributesRTLRO = 0x00000080; - internal const int MsidbDialogAttributesRightAligned = 0x00000100; - internal const int MsidbDialogAttributesLeftScroll = 0x00000200; - internal const int MsidbDialogAttributesBiDi = MsidbDialogAttributesRTLRO | MsidbDialogAttributesRightAligned | MsidbDialogAttributesLeftScroll; - internal const int MsidbDialogAttributesError = 0x00010000; - internal const int CommonControlAttributesInvert = MsidbControlAttributesVisible + MsidbControlAttributesEnabled; - internal const int DialogAttributesInvert = MsidbDialogAttributesVisible + MsidbDialogAttributesModal + MsidbDialogAttributesMinimize; - - // Feature.Attributes - internal const int MsidbFeatureAttributesFavorLocal = 0; - internal const int MsidbFeatureAttributesFavorSource = 1; - internal const int MsidbFeatureAttributesFollowParent = 2; - internal const int MsidbFeatureAttributesFavorAdvertise = 4; - internal const int MsidbFeatureAttributesDisallowAdvertise = 8; - internal const int MsidbFeatureAttributesUIDisallowAbsent = 16; - internal const int MsidbFeatureAttributesNoUnsupportedAdvertise = 32; - - // File.Attributes - internal const int MsidbFileAttributesReadOnly = 1; - internal const int MsidbFileAttributesHidden = 2; - internal const int MsidbFileAttributesSystem = 4; - internal const int MsidbFileAttributesVital = 512; - internal const int MsidbFileAttributesChecksum = 1024; - internal const int MsidbFileAttributesPatchAdded = 4096; - internal const int MsidbFileAttributesNoncompressed = 8192; - internal const int MsidbFileAttributesCompressed = 16384; - - // IniFile.Action & RemoveIniFile.Action - internal const int MsidbIniFileActionAddLine = 0; - internal const int MsidbIniFileActionCreateLine = 1; - internal const int MsidbIniFileActionRemoveLine = 2; - internal const int MsidbIniFileActionAddTag = 3; - internal const int MsidbIniFileActionRemoveTag = 4; - - // MoveFile.Options - internal const int MsidbMoveFileOptionsMove = 1; - - // ServiceInstall.Attributes - internal const int MsidbServiceInstallOwnProcess = 0x00000010; - internal const int MsidbServiceInstallShareProcess = 0x00000020; - internal const int MsidbServiceInstallInteractive = 0x00000100; - internal const int MsidbServiceInstallAutoStart = 0x00000002; - internal const int MsidbServiceInstallDemandStart = 0x00000003; - internal const int MsidbServiceInstallDisabled = 0x00000004; - internal const int MsidbServiceInstallErrorIgnore = 0x00000000; - internal const int MsidbServiceInstallErrorNormal = 0x00000001; - internal const int MsidbServiceInstallErrorCritical = 0x00000003; - internal const int MsidbServiceInstallErrorControlVital = 0x00008000; - - // ServiceConfig.Event - internal const int MsidbServiceConfigEventInstall = 0x00000001; - internal const int MsidbServiceConfigEventUninstall = 0x00000002; - internal const int MsidbServiceConfigEventReinstall = 0x00000004; - - // ServiceControl.Attributes - internal const int MsidbServiceControlEventStart = 0x00000001; - internal const int MsidbServiceControlEventStop = 0x00000002; - internal const int MsidbServiceControlEventDelete = 0x00000008; - internal const int MsidbServiceControlEventUninstallStart = 0x00000010; - internal const int MsidbServiceControlEventUninstallStop = 0x00000020; - internal const int MsidbServiceControlEventUninstallDelete = 0x00000080; - - // TextStyle.StyleBits - internal const int MsidbTextStyleStyleBitsBold = 1; - internal const int MsidbTextStyleStyleBitsItalic = 2; - internal const int MsidbTextStyleStyleBitsUnderline = 4; - internal const int MsidbTextStyleStyleBitsStrike = 8; - - // Upgrade.Attributes - internal const int MsidbUpgradeAttributesMigrateFeatures = 0x00000001; - internal const int MsidbUpgradeAttributesOnlyDetect = 0x00000002; - internal const int MsidbUpgradeAttributesIgnoreRemoveFailure = 0x00000004; - internal const int MsidbUpgradeAttributesVersionMinInclusive = 0x00000100; - internal const int MsidbUpgradeAttributesVersionMaxInclusive = 0x00000200; - internal const int MsidbUpgradeAttributesLanguagesExclusive = 0x00000400; - - // Registry Hive Roots - internal const int MsidbRegistryRootClassesRoot = 0; - internal const int MsidbRegistryRootCurrentUser = 1; - internal const int MsidbRegistryRootLocalMachine = 2; - internal const int MsidbRegistryRootUsers = 3; - - // Locator Types - internal const int MsidbLocatorTypeDirectory = 0; - internal const int MsidbLocatorTypeFileName = 1; - internal const int MsidbLocatorTypeRawValue = 2; - internal const int MsidbLocatorType64bit = 16; - - internal const int MsidbClassAttributesRelativePath = 1; - - // RemoveFile.InstallMode - internal const int MsidbRemoveFileInstallModeOnInstall = 0x00000001; - internal const int MsidbRemoveFileInstallModeOnRemove = 0x00000002; - internal const int MsidbRemoveFileInstallModeOnBoth = 0x00000003; - - // ODBCDataSource.Registration - internal const int MsidbODBCDataSourceRegistrationPerMachine = 0; - internal const int MsidbODBCDataSourceRegistrationPerUser = 1; - - // ModuleConfiguration.Format - internal const int MsidbModuleConfigurationFormatText = 0; - internal const int MsidbModuleConfigurationFormatKey = 1; - internal const int MsidbModuleConfigurationFormatInteger = 2; - internal const int MsidbModuleConfigurationFormatBitfield = 3; - - // ModuleConfiguration.Attributes - internal const int MsidbMsmConfigurableOptionKeyNoOrphan = 1; - internal const int MsidbMsmConfigurableOptionNonNullable = 2; - - // ' Windows API function ShowWindow constants - used in Shortcut table - internal const int SWSHOWNORMAL = 0x00000001; - internal const int SWSHOWMAXIMIZED = 0x00000003; - internal const int SWSHOWMINNOACTIVE = 0x00000007; - - // NameToBit arrays - // UI elements - internal static readonly string[] CommonControlAttributes = { "Hidden", "Disabled", "Sunken", "Indirect", "Integer", "RightToLeft", "RightAligned", "LeftScroll" }; - internal static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; - internal static readonly string[] HyperlinkControlAttributes = { "Transparent" }; - internal static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; - internal static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; - internal static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; - internal static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; - internal static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; - internal static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; - internal static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; - internal static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; - internal static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; - internal static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; - internal static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; - - internal const int MsidbEmbeddedUI = 0x01; - internal const int MsidbEmbeddedHandlesBasic = 0x02; - - internal const int INSTALLLOGMODE_FATALEXIT = 0x00001; - internal const int INSTALLLOGMODE_ERROR = 0x00002; - internal const int INSTALLLOGMODE_WARNING = 0x00004; - internal const int INSTALLLOGMODE_USER = 0x00008; - internal const int INSTALLLOGMODE_INFO = 0x00010; - internal const int INSTALLLOGMODE_FILESINUSE = 0x00020; - internal const int INSTALLLOGMODE_RESOLVESOURCE = 0x00040; - internal const int INSTALLLOGMODE_OUTOFDISKSPACE = 0x00080; - internal const int INSTALLLOGMODE_ACTIONSTART = 0x00100; - internal const int INSTALLLOGMODE_ACTIONDATA = 0x00200; - internal const int INSTALLLOGMODE_PROGRESS = 0x00400; - internal const int INSTALLLOGMODE_COMMONDATA = 0x00800; - internal const int INSTALLLOGMODE_INITIALIZE = 0x01000; - internal const int INSTALLLOGMODE_TERMINATE = 0x02000; - internal const int INSTALLLOGMODE_SHOWDIALOG = 0x04000; - internal const int INSTALLLOGMODE_RMFILESINUSE = 0x02000000; - internal const int INSTALLLOGMODE_INSTALLSTART = 0x04000000; - internal const int INSTALLLOGMODE_INSTALLEND = 0x08000000; - - internal const int MSICONDITIONFALSE = 0; // The table is temporary. - internal const int MSICONDITIONTRUE = 1; // The table is persistent. - internal const int MSICONDITIONNONE = 2; // The table is unknown. - internal const int MSICONDITIONERROR = 3; // An invalid handle or invalid parameter was passed to the function. - - internal const int MSIDBOPENREADONLY = 0; - internal const int MSIDBOPENTRANSACT = 1; - internal const int MSIDBOPENDIRECT = 2; - internal const int MSIDBOPENCREATE = 3; - internal const int MSIDBOPENCREATEDIRECT = 4; - internal const int MSIDBOPENPATCHFILE = 32; - - internal const int MSIMODIFYSEEK = -1; // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks. - internal const int MSIMODIFYREFRESH = 0; // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records. - internal const int MSIMODIFYINSERT = 1; // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYUPDATE = 2; // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records. - internal const int MSIMODIFYASSIGN = 3; // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYREPLACE = 4; // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYMERGE = 5; // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYDELETE = 6; // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYINSERTTEMPORARY = 7; // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYVALIDATE = 8; // Validates a record. Does not validate across joins. You must first call the MsiViewFetch function with the same record. Obtain validation errors with MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYVALIDATENEW = 9; // Validate a new record. Does not validate across joins. Checks for duplicate keys. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYVALIDATEFIELD = 10; // Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. - internal const int MSIMODIFYVALIDATEDELETE = 11; // Validates a record that will be deleted later. You must first call MsiViewFetch. Fails if another row refers to the primary keys of this row. Validation does not check for the existence of the primary keys of this row in properties or strings. Does not check if a column is a foreign key to multiple tables. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. - - internal const uint VTI2 = 2; - internal const uint VTI4 = 3; - internal const uint VTLPWSTR = 30; - internal const uint VTFILETIME = 64; - - internal const int MSICOLINFONAMES = 0; // return column names - internal const int MSICOLINFOTYPES = 1; // return column definitions, datatype code followed by width - - /// - /// Protect the constructor. - /// - private MsiInterop() - { - } - - /// - /// PInvoke of MsiCloseHandle. - /// - /// Handle to a database. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiCloseHandle", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiCloseHandle(uint database); - - /// - /// PInvoke of MsiCreateRecord - /// - /// Count of columns in the record. - /// Handle referencing the record. - [DllImport("msi.dll", EntryPoint = "MsiCreateRecord", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern uint MsiCreateRecord(int parameters); - - /// - /// Creates summary information of an existing transform to include validation and error conditions. - /// - /// The handle to the database that contains the new database summary information. - /// The handle to the database that contains the original summary information. - /// The name of the transform to which the summary information is added. - /// The error conditions that should be suppressed when the transform is applied. - /// Specifies the properties to be validated to verify that the transform can be applied to the database. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiCreateTransformSummaryInfoW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiCreateTransformSummaryInfo(uint database, uint referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations); - - /// - /// Applies a transform to a database. - /// - /// Handle to the database obtained from MsiOpenDatabase to transform. - /// Specifies the name of the transform file to apply. - /// Error conditions that should be suppressed. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseApplyTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseApplyTransform(uint database, string transformFile, TransformErrorConditions errorConditions); - - /// - /// PInvoke of MsiDatabaseCommit. - /// - /// Handle to a databse. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseCommit", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseCommit(uint database); - - /// - /// PInvoke of MsiDatabaseExportW. - /// - /// Handle to a database. - /// Table name. - /// Folder path. - /// File name. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseExportW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseExport(uint database, string tableName, string folderPath, string fileName); - - /// - /// Generates a transform file of differences between two databases. - /// - /// Handle to the database obtained from MsiOpenDatabase that includes the changes. - /// Handle to the database obtained from MsiOpenDatabase that does not include the changes. - /// A null-terminated string that specifies the name of the transform file being generated. - /// This parameter can be null. If szTransformFile is null, you can use MsiDatabaseGenerateTransform to test whether two - /// databases are identical without creating a transform. If the databases are identical, the function returns ERROR_NO_DATA. - /// If the databases are different the function returns NOERROR. - /// This is a reserved argument and must be set to 0. - /// This is a reserved argument and must be set to 0. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseGenerateTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseGenerateTransform(uint database, uint databaseReference, string transformFile, int reserved1, int reserved2); - - /// - /// PInvoke of MsiDatabaseImportW. - /// - /// Handle to a database. - /// Folder path. - /// File name. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseImportW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseImport(uint database, string folderPath, string fileName); - - /// - /// PInvoke of MsiDatabaseMergeW. - /// - /// The handle to the database obtained from MsiOpenDatabase. - /// The handle to the database obtained from MsiOpenDatabase to merge into the base database. - /// The name of the table to receive merge conflict information. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseMergeW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseMerge(uint database, uint databaseMerge, string tableName); - - /// - /// PInvoke of MsiDatabaseOpenViewW. - /// - /// Handle to a database. - /// SQL query. - /// View handle. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseOpenViewW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseOpenView(uint database, string query, out uint view); - - /// - /// PInvoke of MsiGetFileHashW. - /// - /// File path. - /// Hash options (must be 0). - /// Buffer to recieve hash. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiGetFileHashW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiGetFileHash(string filePath, uint options, MSIFILEHASHINFO hash); - - /// - /// PInvoke of MsiGetFileVersionW. - /// - /// File path. - /// Buffer to receive version info. - /// Size of version buffer. - /// Buffer to recieve lang info. - /// Size of lang buffer. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiGetFileVersionW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiGetFileVersion(string filePath, StringBuilder versionBuf, ref int versionBufSize, StringBuilder langBuf, ref int langBufSize); - - /// - /// PInvoke of MsiGetLastErrorRecord. - /// - /// Handle to error record if one exists. - [DllImport("msi.dll", EntryPoint = "MsiGetLastErrorRecord", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern uint MsiGetLastErrorRecord(); - - /// - /// PInvoke of MsiDatabaseGetPrimaryKeysW. - /// - /// Handle to a database. - /// Table name. - /// Handle to receive resulting record. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDatabaseGetPrimaryKeysW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseGetPrimaryKeys(uint database, string tableName, out uint record); - - /// - /// PInvoke of MsiDoActionW. - /// - /// Handle to the installation provided to a DLL custom action or - /// obtained through MsiOpenPackage, MsiOpenPackageEx, or MsiOpenProduct. - /// Specifies the action to execute. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiDoActionW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDoAction(uint product, string action); - - /// - /// PInvoke of MsiGetSummaryInformationW. Can use either database handle or database path as input. - /// - /// Handle to a database. - /// Path to a database. - /// Max number of updated values. - /// Handle to summary information. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiGetSummaryInformationW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiGetSummaryInformation(uint database, string databasePath, uint updateCount, ref uint summaryInfo); - - /// - /// PInvoke of MsiDatabaseIsTablePersitentW. - /// - /// Handle to a database. - /// Table name. - /// MSICONDITION - [DllImport("msi.dll", EntryPoint = "MsiDatabaseIsTablePersistentW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiDatabaseIsTablePersistent(uint database, string tableName); - - /// - /// PInvoke of MsiOpenDatabaseW. - /// - /// Path to database. - /// Persist mode. - /// Handle to database. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiOpenDatabaseW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiOpenDatabase(string databasePath, IntPtr persist, out uint database); - - /// - /// PInvoke of MsiOpenPackageW. - /// - /// The path to the package. - /// A pointer to a variable that receives the product handle. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiOpenPackageW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiOpenPackage(string packagePath, out uint product); - - /// - /// PInvoke of MsiRecordIsNull. - /// - /// MSI Record handle. - /// Index of field to check for null value. - /// true if the field is null, false if not, and an error code for any error. - [DllImport("msi.dll", EntryPoint = "MsiRecordIsNull", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordIsNull(uint record, int field); - - /// - /// PInvoke of MsiRecordGetInteger. - /// - /// MSI Record handle. - /// Index of field to retrieve integer from. - /// Integer value. - [DllImport("msi.dll", EntryPoint = "MsiRecordGetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordGetInteger(uint record, int field); - - /// - /// PInvoke of MsiRectordSetInteger. - /// - /// MSI Record handle. - /// Index of field to set integer value in. - /// Value to set field to. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiRecordSetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordSetInteger(uint record, int field, int value); - - /// - /// PInvoke of MsiRecordGetStringW. - /// - /// MSI Record handle. - /// Index of field to get string value from. - /// Buffer to recieve value. - /// Size of buffer. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiRecordGetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordGetString(uint record, int field, StringBuilder valueBuf, ref int valueBufSize); - - /// - /// PInvoke of MsiRecordSetStringW. - /// - /// MSI Record handle. - /// Index of field to set string value in. - /// String value. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiRecordSetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordSetString(uint record, int field, string value); - - /// - /// PInvoke of MsiRecordSetStreamW. - /// - /// MSI Record handle. - /// Index of field to set stream value in. - /// Path to file to set stream value to. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiRecordSetStreamW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordSetStream(uint record, int field, string filePath); - - /// - /// PInvoke of MsiRecordReadStreamW. - /// - /// MSI Record handle. - /// Index of field to read stream from. - /// Data buffer to recieve stream value. - /// Size of data buffer. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiRecordReadStream", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordReadStream(uint record, int field, byte[] dataBuf, ref int dataBufSize); - - /// - /// PInvoke of MsiRecordGetFieldCount. - /// - /// MSI Record handle. - /// Count of fields in the record. - [DllImport("msi.dll", EntryPoint = "MsiRecordGetFieldCount", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiRecordGetFieldCount(uint record); - - /// - /// PInvoke of MsiSetExternalUIW. - /// - /// Specifies a callback function that conforms to the INSTALLUI_HANDLER specification. - /// Specifies which messages to handle using the external message handler. If the external - /// handler returns a non-zero result, then that message will not be sent to the UI, instead the message will be logged - /// if logging has been enabled. - /// Pointer to an application context that is passed to the callback function. - /// This parameter can be used for error checking. - /// The return value is the previously set external handler, or zero (0) if there was no previously set handler. - [DllImport("msi.dll", EntryPoint = "MsiSetExternalUIW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern InstallUIHandler MsiSetExternalUI(InstallUIHandler installUIHandler, int installLogMode, IntPtr context); - - /// - /// PInvoke of MsiSetInternalUI. - /// - /// Specifies the level of complexity of the user interface. - /// Pointer to a window. This window becomes the owner of any user interface created. - /// A pointer to the previous owner of the user interface is returned. - /// If this parameter is null, the owner of the user interface does not change. - /// The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned. - [DllImport("msi.dll", EntryPoint = "MsiSetInternalUI", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiSetInternalUI(int uiLevel, ref IntPtr hwnd); - - /// - /// PInvoke of MsiSummaryInfoGetPropertyW. - /// - /// Handle to summary info. - /// Property to get value from. - /// Data type of property. - /// Integer to receive integer value. - /// File time to receive file time value. - /// String buffer to receive string value. - /// Size of string buffer. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiSummaryInfoGetPropertyW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiSummaryInfoGetProperty(uint summaryInfo, int property, out uint dataType, out int integerValue, ref FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize); - - /// - /// PInvoke of MsiViewGetColumnInfo. - /// - /// Handle to view. - /// Column info. - /// Handle for returned record. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiViewGetColumnInfo", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiViewGetColumnInfo(uint view, int columnInfo, out uint record); - - /// - /// PInvoke of MsiViewExecute. - /// - /// Handle of view to execute. - /// Handle to a record that supplies the parameters for the view. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiViewExecute", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiViewExecute(uint view, uint record); - - /// - /// PInvoke of MsiViewFetch. - /// - /// Handle of view to fetch a row from. - /// Handle to receive record info. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiViewFetch", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiViewFetch(uint view, out uint record); - - /// - /// PInvoke of MsiViewModify. - /// - /// Handle of view to modify. - /// Modify mode. - /// Handle of record. - /// Error code. - [DllImport("msi.dll", EntryPoint = "MsiViewModify", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int MsiViewModify(uint view, int modifyMode, uint record); - - /// - /// contains the file hash information returned by MsiGetFileHash and used in the MsiFileHash table. - /// - [StructLayout(LayoutKind.Explicit)] - internal class MSIFILEHASHINFO - { - [FieldOffset(0)] internal uint FileHashInfoSize; - [FieldOffset(4)] internal int Data0; - [FieldOffset(8)] internal int Data1; - [FieldOffset(12)]internal int Data2; - [FieldOffset(16)]internal int Data3; - } - } -} -#endif \ No newline at end of file diff --git a/src/WixToolset.Core/Msi/MsiException.cs b/src/WixToolset.Core/Msi/MsiException.cs deleted file mode 100644 index b33bf27a..00000000 --- a/src/WixToolset.Core/Msi/MsiException.cs +++ /dev/null @@ -1,78 +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.Msi -{ - using System; - using System.ComponentModel; - using WixToolset.Core.Native; - - /// - /// Exception that wraps MsiGetLastError(). - /// - [Serializable] - public class MsiException : Win32Exception - { - /// - /// Instantiate a new MsiException with a given error. - /// - /// The error code from the MsiXxx() function call. - public MsiException(int error) : base(error) - { - uint handle = MsiInterop.MsiGetLastErrorRecord(); - if (0 != handle) - { - using (Record record = new Record(handle)) - { - this.MsiError = record.GetInteger(1); - - int errorInfoCount = record.GetFieldCount() - 1; - this.ErrorInfo = new string[errorInfoCount]; - for (int i = 0; i < errorInfoCount; ++i) - { - this.ErrorInfo[i] = record.GetString(i + 2); - } - } - } - else - { - this.MsiError = 0; - this.ErrorInfo = new string[0]; - } - - this.Error = error; - } - - /// - /// Gets the error number. - /// - public int Error { get; private set; } - - /// - /// Gets the internal MSI error number. - /// - public int MsiError { get; private set; } - - /// - /// Gets any additional the error information. - /// - public string[] ErrorInfo { get; private set; } - - /// - /// Overrides Message property to return useful error message. - /// - public override string Message - { - get - { - if (0 == this.MsiError) - { - return base.Message; - } - else - { - return String.Format("Internal MSI failure. Win32 error: {0}, MSI error: {1}, detail: {2}", this.Error, this.MsiError, String.Join(", ", this.ErrorInfo)); - } - } - } - } -} diff --git a/src/WixToolset.Core/Msi/MsiHandle.cs b/src/WixToolset.Core/Msi/MsiHandle.cs deleted file mode 100644 index 6d2dc984..00000000 --- a/src/WixToolset.Core/Msi/MsiHandle.cs +++ /dev/null @@ -1,116 +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.Msi -{ - using System; - using System.ComponentModel; - using System.Diagnostics; - using System.Threading; - using WixToolset.Core.Native; - - /// - /// Wrapper class for MSI handle. - /// - public class MsiHandle : IDisposable - { - private bool disposed; - private uint handle; - private int owningThread; -#if DEBUG - private string creationStack; -#endif - - /// - /// MSI handle destructor. - /// - ~MsiHandle() - { - this.Dispose(false); - } - - /// - /// Gets or sets the MSI handle. - /// - /// The MSI handle. - internal uint Handle - { - get - { - if (this.disposed) - { - throw new ObjectDisposedException("MsiHandle"); - } - - return this.handle; - } - - set - { - if (this.disposed) - { - throw new ObjectDisposedException("MsiHandle"); - } - - this.handle = value; - this.owningThread = Thread.CurrentThread.ManagedThreadId; -#if DEBUG - this.creationStack = Environment.StackTrace; -#endif - } - } - - /// - /// Close the MSI handle. - /// - public void Close() - { - this.Dispose(); - } - - /// - /// Disposes the managed and unmanaged objects in this object. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes the managed and unmanaged objects in this object. - /// - /// true to dispose the managed objects. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (0 != this.handle) - { - if (Thread.CurrentThread.ManagedThreadId == this.owningThread) - { - int error = MsiInterop.MsiCloseHandle(this.handle); - if (0 != error) - { - throw new Win32Exception(error); - } - this.handle = 0; - } - else - { - // Don't try to close the handle on a different thread than it was opened. - // This will occasionally cause MSI to AV. - string message = String.Format("Leaked msi handle {0} created on thread {1} by type {2}. This handle cannot be closed on thread {3}", - this.handle, this.owningThread, this.GetType(), Thread.CurrentThread.ManagedThreadId); -#if DEBUG - throw new InvalidOperationException(String.Format("{0}. Created {1}", message, this.creationStack)); -#else - Debug.WriteLine(message); -#endif - } - } - - this.disposed = true; - } - } - } -} diff --git a/src/WixToolset.Core/Msi/Record.cs b/src/WixToolset.Core/Msi/Record.cs deleted file mode 100644 index 438aa3b0..00000000 --- a/src/WixToolset.Core/Msi/Record.cs +++ /dev/null @@ -1,182 +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.Msi -{ - using System; - using System.ComponentModel; - using System.Text; - using WixToolset.Core.Native; - - /// - /// Wrapper class around msi.dll interop for a record. - /// - public sealed class Record : MsiHandle - { - /// - /// Creates a record with the specified number of fields. - /// - /// Number of fields in record. - public Record(int fieldCount) - { - this.Handle = MsiInterop.MsiCreateRecord(fieldCount); - if (0 == this.Handle) - { - throw new OutOfMemoryException(); - } - } - - /// - /// Creates a record from a handle. - /// - /// Handle to create record from. - internal Record(uint handle) - { - this.Handle = handle; - } - - /// - /// Gets a string value at specified location. - /// - /// Index into record to get string. - public string this[int field] - { - get { return this.GetString(field); } - set { this.SetString(field, (string)value); } - } - - /// - /// Determines if the value is null at the specified location. - /// - /// Index into record of the field to query. - /// true if the value is null, false otherwise. - public bool IsNull(int field) - { - int error = MsiInterop.MsiRecordIsNull(this.Handle, field); - - switch (error) - { - case 0: - return false; - case 1: - return true; - default: - throw new Win32Exception(error); - } - } - - /// - /// Gets integer value at specified location. - /// - /// Index into record to get integer - /// Integer value - public int GetInteger(int field) - { - return MsiInterop.MsiRecordGetInteger(this.Handle, field); - } - - /// - /// Sets integer value at specified location. - /// - /// Index into record to set integer. - /// Value to set into record. - public void SetInteger(int field, int value) - { - int error = MsiInterop.MsiRecordSetInteger(this.Handle, field, value); - if (0 != error) - { - throw new Win32Exception(error); - } - } - - /// - /// Gets string value at specified location. - /// - /// Index into record to get string. - /// String value - public string GetString(int field) - { - int bufferSize = 255; - StringBuilder buffer = new StringBuilder(bufferSize); - int error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize); - if (234 == error) - { - buffer.EnsureCapacity(++bufferSize); - error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize); - } - - if (0 != error) - { - throw new Win32Exception(error); - } - - return (0 < buffer.Length ? buffer.ToString() : null); - } - - /// - /// Set string value at specified location - /// - /// Index into record to set string. - /// Value to set into record - public void SetString(int field, string value) - { - int error = MsiInterop.MsiRecordSetString(this.Handle, field, value); - if (0 != error) - { - throw new Win32Exception(error); - } - } - - /// - /// Get stream at specified location. - /// - /// Index into record to get stream. - /// buffer to receive bytes from stream. - /// Buffer size to read. - /// Stream read into string. - public int GetStream(int field, byte[] buffer, int requestedBufferSize) - { - int bufferSize = 255; - if (requestedBufferSize > 0) - { - bufferSize = requestedBufferSize; - } - - int error = MsiInterop.MsiRecordReadStream(this.Handle, field, buffer, ref bufferSize); - if (0 != error) - { - throw new Win32Exception(error); - } - - return bufferSize; - } - - /// - /// Sets a stream at a specified location. - /// - /// Index into record to set stream. - /// Path to file to read into stream. - public void SetStream(int field, string path) - { - int error = MsiInterop.MsiRecordSetStream(this.Handle, field, path); - if (0 != error) - { - throw new Win32Exception(error); - } - } - - /// - /// Gets the number of fields in record. - /// - /// Count of fields in record. - public int GetFieldCount() - { - int size = MsiInterop.MsiRecordGetFieldCount(this.Handle); - if (0 > size) - { - throw new Win32Exception(); - } - - return size; - } - } -} diff --git a/src/WixToolset.Core/Msi/Session.cs b/src/WixToolset.Core/Msi/Session.cs deleted file mode 100644 index d3a19711..00000000 --- a/src/WixToolset.Core/Msi/Session.cs +++ /dev/null @@ -1,45 +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.Msi -{ - using System; - using System.ComponentModel; - using System.Globalization; - using WixToolset.Core.Native; - - /// - /// Controls the installation process. - /// - internal sealed class Session : MsiHandle - { - /// - /// Instantiate a new Session. - /// - /// The database to open. - public Session(Database database) - { - string packagePath = String.Format(CultureInfo.InvariantCulture, "#{0}", (uint)database.Handle); - - uint handle = 0; - int error = MsiInterop.MsiOpenPackage(packagePath, out handle); - if (0 != error) - { - throw new MsiException(error); - } - this.Handle = handle; - } - - /// - /// Executes a built-in action, custom action, or user-interface wizard action. - /// - /// Specifies the action to execute. - public void DoAction(string action) - { - int error = MsiInterop.MsiDoAction(this.Handle, action); - if (0 != error) - { - throw new MsiException(error); - } - } - } -} diff --git a/src/WixToolset.Core/Msi/SummaryInformation.cs b/src/WixToolset.Core/Msi/SummaryInformation.cs deleted file mode 100644 index 39949db6..00000000 --- a/src/WixToolset.Core/Msi/SummaryInformation.cs +++ /dev/null @@ -1,323 +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.Msi -{ - using System; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Text; - using System.Runtime.InteropServices; - using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; - using WixToolset.Core.Native; - - /// - /// Summary information for the MSI files. - /// - internal sealed class SummaryInformation : MsiHandle - { - /// - /// Summary information properties for transforms. - /// - public enum Transform - { - /// PID_CODEPAGE = code page for the summary information stream - CodePage = 1, - - /// PID_TITLE = typically just "Transform" - Title = 2, - - /// PID_SUBJECT = original subject of target - TargetSubject = 3, - - /// PID_AUTHOR = original manufacturer of target - TargetManufacturer = 4, - - /// PID_KEYWORDS = keywords for the transform, typically including at least "Installer" - Keywords = 5, - - /// PID_COMMENTS = describes what this package does - Comments = 6, - - /// PID_TEMPLATE = target platform;language - TargetPlatformAndLanguage = 7, - - /// PID_LASTAUTHOR = updated platform;language - UpdatedPlatformAndLanguage = 8, - - /// PID_REVNUMBER = {productcode}version;{newproductcode}newversion;upgradecode - ProductCodes = 9, - - /// PID_LASTPRINTED should be null for transforms - Reserved11 = 11, - - ///.PID_CREATE_DTM = the timestamp when the transform was created - CreationTime = 12, - - /// PID_PAGECOUNT = minimum installer version - InstallerRequirement = 14, - - /// PID_CHARCOUNT = validation and error flags - ValidationFlags = 16, - - /// PID_APPNAME = the application that created the transform - CreatingApplication = 18, - - /// PID_SECURITY = whether read-only is enforced; should always be 4 for transforms - Security = 19, - } - - /// - /// Summary information properties for patches. - /// - public enum Patch - { - /// PID_CODEPAGE = code page of the summary information stream - CodePage = 1, - - /// PID_TITLE = a brief description of the package type - Title = 2, - - /// PID_SUBJECT = package name - PackageName = 3, - - /// PID_AUTHOR = manufacturer of the patch package - Manufacturer = 4, - - /// PID_KEYWORDS = alternate sources for the patch package - Sources = 5, - - /// PID_COMMENTS = general purpose of the patch package - Comments = 6, - - /// PID_TEMPLATE = semicolon delimited list of ProductCodes - ProductCodes = 7, - - /// PID_LASTAUTHOR = semicolon delimited list of transform names - TransformNames = 8, - - /// PID_REVNUMBER = GUID patch code - PatchCode = 9, - - /// PID_LASTPRINTED should be null for patches - Reserved11 = 11, - - /// PID_PAGECOUNT should be null for patches - Reserved14 = 14, - - /// PID_WORDCOUNT = minimum installer version - InstallerRequirement = 15, - - /// PID_CHARCOUNT should be null for patches - Reserved16 = 16, - - /// PID_SECURITY = read-only attribute of the patch package - Security = 19, - } - - /// - /// Summary information values for the InstallerRequirement property. - /// - public enum InstallerRequirement - { - /// Any version of the installer will do - Version10 = 1, - - /// At least 1.2 - Version12 = 2, - - /// At least 2.0 - Version20 = 3, - - /// At least 3.0 - Version30 = 4, - - /// At least 3.1 - Version31 = 5, - } - - /// - /// Instantiate a new SummaryInformation class from an open database. - /// - /// Database to retrieve summary information from. - public SummaryInformation(Database db) - { - if (null == db) - { - throw new ArgumentNullException("db"); - } - - uint handle = 0; - int error = MsiInterop.MsiGetSummaryInformation(db.Handle, null, 0, ref handle); - if (0 != error) - { - throw new MsiException(error); - } - this.Handle = handle; - } - - /// - /// Instantiate a new SummaryInformation class from a database file. - /// - /// The database file. - public SummaryInformation(string databaseFile) - { - if (null == databaseFile) - { - throw new ArgumentNullException("databaseFile"); - } - - uint handle = 0; - int error = MsiInterop.MsiGetSummaryInformation(0, databaseFile, 0, ref handle); - if (0 != error) - { - throw new MsiException(error); - } - this.Handle = handle; - } - - /// - /// Variant types in the summary information table. - /// - private enum VT : uint - { - /// Variant has not been assigned. - EMPTY = 0, - - /// Null variant type. - NULL = 1, - - /// 16-bit integer variant type. - I2 = 2, - - /// 32-bit integer variant type. - I4 = 3, - - /// String variant type. - LPSTR = 30, - - /// Date time (FILETIME, converted to Variant time) variant type. - FILETIME = 64, - } - - /// - /// Gets a summary information property. - /// - /// Index of the summary information property. - /// The summary information property. - public string GetProperty(int index) - { - uint dataType; - StringBuilder stringValue = new StringBuilder(""); - int bufSize = 0; - int intValue; - FILETIME timeValue; - timeValue.dwHighDateTime = 0; - timeValue.dwLowDateTime = 0; - - int error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize); - if (234 == error) - { - stringValue.EnsureCapacity(++bufSize); - error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize); - } - - if (0 != error) - { - throw new MsiException(error); - } - - switch ((VT)dataType) - { - case VT.EMPTY: - return String.Empty; - case VT.LPSTR: - return stringValue.ToString(); - case VT.I2: - case VT.I4: - return Convert.ToString(intValue, CultureInfo.InvariantCulture); - case VT.FILETIME: - long longFileTime = (((long)timeValue.dwHighDateTime) << 32) | unchecked((uint)timeValue.dwLowDateTime); - DateTime dateTime = DateTime.FromFileTime(longFileTime); - return dateTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); - default: - throw new InvalidOperationException(); - } - } - } - - /// - /// Summary information values for the CharCount property in transforms. - /// - [Flags] - [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] - public enum TransformFlags - { - /// Ignore error when adding a row that exists. - ErrorAddExistingRow = 0x1, - - /// Ignore error when deleting a row that does not exist. - ErrorDeleteMissingRow = 0x2, - - /// Ignore error when adding a table that exists. - ErrorAddExistingTable = 0x4, - - /// Ignore error when deleting a table that does not exist. - ErrorDeleteMissingTable = 0x8, - - /// Ignore error when updating a row that does not exist. - ErrorUpdateMissingRow = 0x10, - - /// Ignore error when transform and database code pages do not match, and their code pages are neutral. - ErrorChangeCodePage = 0x20, - - /// Default language must match base database. - ValidateLanguage = 0x10000, - - /// Product must match base database. - ValidateProduct = 0x20000, - - /// Check major version only. - ValidateMajorVersion = 0x80000, - - /// Check major and minor versions only. - ValidateMinorVersion = 0x100000, - - /// Check major, minor, and update versions. - ValidateUpdateVersion = 0x200000, - - /// Installed version lt base version. - ValidateNewLessBaseVersion = 0x400000, - - /// Installed version lte base version. - ValidateNewLessEqualBaseVersion = 0x800000, - - /// Installed version eq base version. - ValidateNewEqualBaseVersion = 0x1000000, - - /// Installed version gte base version. - ValidateNewGreaterEqualBaseVersion = 0x2000000, - - /// Installed version gt base version. - ValidateNewGreaterBaseVersion = 0x4000000, - - /// UpgradeCode must match base database. - ValidateUpgradeCode = 0x8000000, - - /// Masks all version checks on ProductVersion. - ProductVersionMask = ValidateMajorVersion | ValidateMinorVersion | ValidateUpdateVersion, - - /// Masks all operations on ProductVersion. - ProductVersionOperatorMask = ValidateNewLessBaseVersion | ValidateNewLessEqualBaseVersion | ValidateNewEqualBaseVersion | ValidateNewGreaterEqualBaseVersion | ValidateNewGreaterBaseVersion, - - /// Default value for instance transforms. - InstanceTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct | ValidateUpdateVersion | ValidateNewGreaterEqualBaseVersion, - - /// Default value for language transforms. - LanguageTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct, - - /// Default value for patch transforms. - PatchTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ValidateProduct | ValidateUpdateVersion | ValidateNewEqualBaseVersion | ValidateUpgradeCode, - } - -} diff --git a/src/WixToolset.Core/Msi/View.cs b/src/WixToolset.Core/Msi/View.cs deleted file mode 100644 index d6542824..00000000 --- a/src/WixToolset.Core/Msi/View.cs +++ /dev/null @@ -1,189 +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.Msi -{ - using System; - using System.ComponentModel; - using System.Globalization; - using WixToolset.Core.Native; - - /// - /// Enumeration of different modify modes. - /// - public enum ModifyView - { - /// - /// Writes current data in the cursor to a table row. Updates record if the primary - /// keys match an existing row and inserts if they do not match. Fails with a read-only - /// database. This mode cannot be used with a view containing joins. - /// - Assign = MsiInterop.MSIMODIFYASSIGN, - - /// - /// Remove a row from the table. You must first call the Fetch function with the same - /// record. Fails if the row has been deleted. Works only with read-write records. This - /// mode cannot be used with a view containing joins. - /// - Delete = MsiInterop.MSIMODIFYDELETE, - - /// - /// Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only - /// database. This mode cannot be used with a view containing joins. - /// - Insert = MsiInterop.MSIMODIFYINSERT, - - /// - /// Inserts a temporary record. The information is not persistent. Fails if a row with the - /// same primary key exists. Works only with read-write records. This mode cannot be - /// used with a view containing joins. - /// - InsertTemporary = MsiInterop.MSIMODIFYINSERTTEMPORARY, - - /// - /// Inserts or validates a record in a table. Inserts if primary keys do not match any row - /// and validates if there is a match. Fails if the record does not match the data in - /// the table. Fails if there is a record with a duplicate key that is not identical. - /// Works only with read-write records. This mode cannot be used with a view containing joins. - /// - Merge = MsiInterop.MSIMODIFYMERGE, - - /// - /// Refreshes the information in the record. Must first call Fetch with the - /// same record. Fails for a deleted row. Works with read-write and read-only records. - /// - Refresh = MsiInterop.MSIMODIFYREFRESH, - - /// - /// Updates or deletes and inserts a record into a table. Must first call Fetch with - /// the same record. Updates record if the primary keys are unchanged. Deletes old row and - /// inserts new if primary keys have changed. Fails with a read-only database. This mode cannot - /// be used with a view containing joins. - /// - Replace = MsiInterop.MSIMODIFYREPLACE, - - /// - /// Refreshes the information in the supplied record without changing the position in the - /// result set and without affecting subsequent fetch operations. The record may then - /// be used for subsequent Update, Delete, and Refresh. All primary key columns of the - /// table must be in the query and the record must have at least as many fields as the - /// query. Seek cannot be used with multi-table queries. This mode cannot be used with - /// a view containing joins. See also the remarks. - /// - Seek = MsiInterop.MSIMODIFYSEEK, - - /// - /// Updates an existing record. Non-primary keys only. Must first call Fetch. Fails with a - /// deleted record. Works only with read-write records. - /// - Update = MsiInterop.MSIMODIFYUPDATE - } - - /// - /// Wrapper class for MSI API views. - /// - internal sealed class View : MsiHandle - { - /// - /// Constructor that creates a view given a database handle and a query. - /// - /// Handle to the database to run the query on. - /// Query to be executed. - public View(Database db, string query) - { - if (null == db) - { - throw new ArgumentNullException("db"); - } - - if (null == query) - { - throw new ArgumentNullException("query"); - } - - uint handle = 0; - - int error = MsiInterop.MsiDatabaseOpenView(db.Handle, query, out handle); - if (0 != error) - { - throw new MsiException(error); - } - - this.Handle = handle; - } - - /// - /// Executes a view with no customizable parameters. - /// - public void Execute() - { - this.Execute(null); - } - - /// - /// Executes a query substituing the values from the records into the customizable parameters - /// in the view. - /// - /// Record containing parameters to be substituded into the view. - public void Execute(Record record) - { - int error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle); - if (0 != error) - { - throw new MsiException(error); - } - } - - /// - /// Fetches the next row in the view. - /// - /// Returns the fetched record; otherwise null. - public Record Fetch() - { - uint recordHandle; - - int error = MsiInterop.MsiViewFetch(this.Handle, out recordHandle); - if (259 == error) - { - return null; - } - else if (0 != error) - { - throw new MsiException(error); - } - - return new Record(recordHandle); - } - - /// - /// Updates a fetched record. - /// - /// Type of modification mode. - /// Record to be modified. - public void Modify(ModifyView type, Record record) - { - int error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle); - if (0 != error) - { - throw new MsiException(error); - } - } - - /// - /// Returns a record containing column names or definitions. - /// - /// Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES. - /// The record containing information about the column. - public Record GetColumnInfo(int columnType) - { - uint recordHandle; - - int error = MsiInterop.MsiViewGetColumnInfo(this.Handle, columnType, out recordHandle); - if (0 != error) - { - throw new MsiException(error); - } - - return new Record(recordHandle); - } - } -} diff --git a/src/WixToolset.Core/Ole32/Storage.cs b/src/WixToolset.Core/Ole32/Storage.cs deleted file mode 100644 index c6a43bc4..00000000 --- a/src/WixToolset.Core/Ole32/Storage.cs +++ /dev/null @@ -1,437 +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.Ole32 -{ - using System; - using System.Runtime.InteropServices; - using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; - using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; - - /// - /// Specifies the access mode to use when opening, creating, or deleting a storage object. - /// - internal enum StorageMode - { - /// - /// Indicates that the object is read-only, meaning that modifications cannot be made. - /// - Read = 0x0, - - /// - /// Enables you to save changes to the object, but does not permit access to its data. - /// - Write = 0x1, - - /// - /// Enables access and modification of object data. - /// - ReadWrite = 0x2, - - /// - /// Specifies that subsequent openings of the object are not denied read or write access. - /// - ShareDenyNone = 0x40, - - /// - /// Prevents others from subsequently opening the object in Read mode. - /// - ShareDenyRead = 0x30, - - /// - /// Prevents others from subsequently opening the object for Write or ReadWrite access. - /// - ShareDenyWrite = 0x20, - - /// - /// Prevents others from subsequently opening the object in any mode. - /// - ShareExclusive = 0x10, - - /// - /// Opens the storage object with exclusive access to the most recently committed version. - /// - Priority = 0x40000, - - /// - /// Indicates that an existing storage object or stream should be removed before the new object replaces it. - /// - Create = 0x1000, - } - - /// - /// Wrapper for the compound storage file APIs. - /// - internal sealed class Storage : IDisposable - { - private bool disposed; - private IStorage storage; - - /// - /// Instantiate a new Storage. - /// - /// The native storage interface. - private Storage(IStorage storage) - { - this.storage = storage; - } - - /// - /// Storage destructor. - /// - ~Storage() - { - this.Dispose(); - } - - /// - /// The IEnumSTATSTG interface enumerates an array of STATSTG structures. - /// - [ComImport, Guid("0000000d-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IEnumSTATSTG - { - /// - /// Gets a specified number of STATSTG structures. - /// - /// The number of STATSTG structures requested. - /// An array of STATSTG structures returned. - /// The number of STATSTG structures retrieved in the rgelt parameter. - /// The error code. - [PreserveSig] - uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] rgelt, out uint pceltFetched); - - /// - /// Skips a specified number of STATSTG structures in the enumeration sequence. - /// - /// The number of STATSTG structures to skip. - void Skip(uint celt); - - /// - /// Resets the enumeration sequence to the beginning of the STATSTG structure array. - /// - void Reset(); - - /// - /// Creates a new enumerator that contains the same enumeration state as the current STATSTG structure enumerator. - /// - /// The cloned IEnumSTATSTG interface. - [return: MarshalAs(UnmanagedType.Interface)] - IEnumSTATSTG Clone(); - } - - /// - /// The IStorage interface supports the creation and management of structured storage objects. - /// - [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - private interface IStorage - { - /// - /// Creates and opens a stream object with the specified name contained in this storage object. - /// - /// The name of the newly created stream. - /// Specifies the access mode to use when opening the newly created stream. - /// Reserved for future use; must be zero. - /// Reserved for future use; must be zero. - /// On return, pointer to the location of the new IStream interface pointer. - void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm); - - /// - /// Opens an existing stream object within this storage object using the specified access permissions in grfMode. - /// - /// The name of the stream to open. - /// Reserved for future use; must be NULL. - /// Specifies the access mode to be assigned to the open stream. - /// Reserved for future use; must be zero. - /// A pointer to IStream pointer variable that receives the interface pointer to the newly opened stream object. - void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm); - - /// - /// Creates and opens a new storage object nested within this storage object with the specified name in the specified access mode. - /// - /// The name of the newly created storage object. - /// A value that specifies the access mode to use when opening the newly created storage object. - /// Reserved for future use; must be zero. - /// Reserved for future use; must be zero. - /// A pointer, when successful, to the location of the IStorage pointer to the newly created storage object. - void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg); - - /// - /// Opens an existing storage object with the specified name in the specified access mode. - /// - /// The name of the storage object to open. - /// Must be NULL. - /// Specifies the access mode to use when opening the storage object. - /// Must be NULL. - /// Reserved for future use; must be zero. - /// When successful, pointer to the location of an IStorage pointer to the opened storage object. - void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg); - - /// - /// Copies the entire contents of an open storage object to another storage object. - /// - /// The number of elements in the array pointed to by rgiidExclude. - /// An array of interface identifiers (IIDs) that either the caller knows about and does not want - /// copied or that the storage object does not support, but whose state the caller will later explicitly copy. - /// A string name block (refer to SNB) that specifies a block of storage or stream objects that are not to be copied to the destination. - /// A pointer to the open storage object into which this storage object is to be copied. - void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest); - - /// - /// Copies or moves a substorage or stream from this storage object to another storage object. - /// - /// The name of the element in this storage object to be moved or copied. - /// IStorage pointer to the destination storage object. - /// The new name for the element in its new storage object. - /// Specifies whether the operation should be a move (STGMOVE_MOVE) or a copy (STGMOVE_COPY). - void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags); - - /// - /// Reflects changes for a transacted storage object to the parent level. - /// - /// Controls how the changes are committed to the storage object. - void Commit(uint grfCommitFlags); - - /// - /// Discards all changes that have been made to the storage object since the last commit operation. - /// - void Revert(); - - /// - /// Returns an enumerator object that can be used to enumerate the storage and stream objects contained within this storage object. - /// - /// Reserved for future use; must be zero. - /// Reserved for future use; must be NULL. - /// Reserved for future use; must be zero. - /// Pointer to IEnumSTATSTG* pointer variable that receives the interface pointer to the new enumerator object. - void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum); - - /// - /// Removes the specified storage or stream from this storage object. - /// - /// The name of the storage or stream to be removed. - void DestroyElement(string pwcsName); - - /// - /// Renames the specified storage or stream in this storage object. - /// - /// The name of the substorage or stream to be changed. - /// The new name for the specified substorage or stream. - void RenameElement(string pwcsOldName, string pwcsNewName); - - /// - /// Sets the modification, access, and creation times of the indicated storage element, if supported by the underlying file system. - /// - /// The name of the storage object element whose times are to be modified. - /// Either the new creation time for the element or NULL if the creation time is not to be modified. - /// Either the new access time for the element or NULL if the access time is not to be modified. - /// Either the new modification time for the element or NULL if the modification time is not to be modified. - void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime); - - /// - /// Assigns the specified CLSID to this storage object. - /// - /// The CLSID that is to be associated with the storage object. - void SetClass(Guid clsid); - - /// - /// Stores up to 32 bits of state information in this storage object. - /// - /// Specifies the new values of the bits to set. - /// A binary mask indicating which bits in grfStateBits are significant in this call. - void SetStateBits(uint grfStateBits, uint grfMask); - - /// - /// Returns the STATSTG structure for this open storage object. - /// - /// On return, pointer to a STATSTG structure where this method places information about the open storage object. - /// Specifies that some of the members in the STATSTG structure are not returned, thus saving a memory allocation operation. - void Stat(out STATSTG pstatstg, uint grfStatFlag); - } - - /// - /// The IStream interface lets you read and write data to stream objects. - /// - [ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - private interface IStream - { - /// - /// Reads a specified number of bytes from the stream object into memory starting at the current seek pointer. - /// - /// A pointer to the buffer which the stream data is read into. - /// The number of bytes of data to read from the stream object. - /// A pointer to a ULONG variable that receives the actual number of bytes read from the stream object. - void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead); - - /// - /// Writes a specified number of bytes into the stream object starting at the current seek pointer. - /// - /// A pointer to the buffer that contains the data that is to be written to the stream. - /// The number of bytes of data to attempt to write into the stream. - /// A pointer to a ULONG variable where this method writes the actual number of bytes written to the stream object. - void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten); - - /// - /// Changes the seek pointer to a new location relative to the beginning of the stream, the end of the stream, or the current seek pointer. - /// - /// The displacement to be added to the location indicated by the dwOrigin parameter. - /// The origin for the displacement specified in dlibMove. - /// A pointer to the location where this method writes the value of the new seek pointer from the beginning of the stream. - void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition); - - /// - /// Changes the size of the stream object. - /// - /// Specifies the new size of the stream as a number of bytes. - void SetSize(long libNewSize); - - /// - /// Copies a specified number of bytes from the current seek pointer in the stream to the current seek pointer in another stream. - /// - /// A pointer to the destination stream. - /// The number of bytes to copy from the source stream. - /// A pointer to the location where this method writes the actual number of bytes read from the source. - /// A pointer to the location where this method writes the actual number of bytes written to the destination. - void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten); - - /// - /// Ensures that any changes made to a stream object open in transacted mode are reflected in the parent storage object. - /// - /// Controls how the changes for the stream object are committed. - void Commit(int grfCommitFlags); - - /// - /// Discards all changes that have been made to a transacted stream since the last call to IStream::Commit. - /// - void Revert(); - - /// - /// Restricts access to a specified range of bytes in the stream. - /// - /// Integer that specifies the byte offset for the beginning of the range. - /// Integer that specifies the length of the range, in bytes, to be restricted. - /// Specifies the restrictions being requested on accessing the range. - void LockRegion(long libOffset, long cb, int dwLockType); - - /// - /// Removes the access restriction on a range of bytes previously restricted with IStream::LockRegion. - /// - /// Specifies the byte offset for the beginning of the range. - /// Specifies, in bytes, the length of the range to be restricted. - /// Specifies the access restrictions previously placed on the range. - void UnlockRegion(long libOffset, long cb, int dwLockType); - - /// - /// Retrieves the STATSTG structure for this stream. - /// - /// Pointer to a STATSTG structure where this method places information about this stream object. - /// Specifies that this method does not return some of the members in the STATSTG structure, thus saving a memory allocation operation. - void Stat(out STATSTG pstatstg, int grfStatFlag); - - /// - /// Creates a new stream object that references the same bytes as the original stream but provides a separate seek pointer to those bytes. - /// - /// When successful, pointer to the location of an IStream pointer to the new stream object. - void Clone(out IStream ppstm); - } - - /// - /// Creates a new compound file storage object. - /// - /// The compound file being created. - /// Specifies the access mode to use when opening the new storage object. - /// The created Storage object. - public static Storage CreateDocFile(string storageFile, StorageMode mode) - { - IStorage storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0); - - return new Storage(storage); - } - - /// - /// Opens an existing root storage object in the file system. - /// - /// The file that contains the storage object to open. - /// Specifies the access mode to use to open the storage object. - /// The created Storage object. - public static Storage Open(string storageFile, StorageMode mode) - { - IStorage storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0); - - return new Storage(storage); - } - - /// - /// Copies the entire contents of this open storage object into another Storage object. - /// - /// The destination Storage object. - public void CopyTo(Storage destinationStorage) - { - this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage); - } - - /// - /// Opens an existing Storage object with the specified name according to the specified access mode. - /// - /// The name of the Storage object. - /// The opened Storage object. - public Storage OpenStorage(string name) - { - IStorage subStorage; - - this.storage.OpenStorage(name, null, (uint)(StorageMode.Read | StorageMode.ShareExclusive), IntPtr.Zero, 0, out subStorage); - - return new Storage(subStorage); - } - - /// - /// Disposes the managed and unmanaged objects in this object. - /// - public void Dispose() - { - if (!this.disposed) - { - Marshal.ReleaseComObject(this.storage); - - this.disposed = true; - } - - GC.SuppressFinalize(this); - } - - /// - /// The native methods. - /// - private sealed class NativeMethods - { - /// - /// Protect the constructor since this class only contains static methods. - /// - private NativeMethods() - { - } - - /// - /// Creates a new compound file storage object. - /// - /// The name for the compound file being created. - /// Specifies the access mode to use when opening the new storage object. - /// Reserved for future use; must be zero. - /// A pointer to the location of the IStorage pointer to the new storage object. - [DllImport("ole32.dll", PreserveSig = false)] - [return: MarshalAs(UnmanagedType.Interface)] - internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved); - - /// - /// Opens an existing root storage object in the file system. - /// - /// The file that contains the storage object to open. - /// Most often NULL. - /// Specifies the access mode to use to open the storage object. - /// If not NULL, pointer to a block of elements in the storage to be excluded as the storage object is opened. - /// Indicates reserved for future use; must be zero. - /// A pointer to a IStorage* pointer variable that receives the interface pointer to the opened storage. - [DllImport("ole32.dll", PreserveSig = false)] - [return: MarshalAs(UnmanagedType.Interface)] - internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved); - } - } -} diff --git a/src/WixToolset.Core/OptimizeCA.cs b/src/WixToolset.Core/OptimizeCA.cs new file mode 100644 index 00000000..efd07299 --- /dev/null +++ b/src/WixToolset.Core/OptimizeCA.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using System; + + /// + /// Values for the OptimizeCA MsiPatchMetdata property, which indicates whether custom actions can be skipped when applying the patch. + /// + [Flags] + public enum OptimizeCA + { + /// + /// No custom actions are skipped. + /// + None = 0, + + /// + /// Skip property (type 51) and directory (type 35) assignment custom actions. + /// + SkipAssignment = 1, + + /// + /// Skip immediate custom actions that are not property or directory assignment custom actions. + /// + SkipImmediate = 2, + + /// + /// Skip custom actions that run within the script. + /// + SkipDeferred = 4, + } +} diff --git a/src/WixToolset.Core/Patch.cs b/src/WixToolset.Core/Patch.cs deleted file mode 100644 index e3e6c27f..00000000 --- a/src/WixToolset.Core/Patch.cs +++ /dev/null @@ -1,1284 +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.Data -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using WixToolset.Data.Rows; - using WixToolset.Extensibility; - using WixToolset.Msi; - using WixToolset.Core.Native; - - /// - /// Values for the OptimizeCA MsiPatchMetdata property, which indicates whether custom actions can be skipped when applying the patch. - /// - [Flags] - internal enum OptimizeCA - { - /// - /// No custom actions are skipped. - /// - None = 0, - - /// - /// Skip property (type 51) and directory (type 35) assignment custom actions. - /// - SkipAssignment = 1, - - /// - /// Skip immediate custom actions that are not property or directory assignment custom actions. - /// - SkipImmediate = 2, - - /// - /// Skip custom actions that run within the script. - /// - SkipDeferred = 4, - } - - /// - /// Contains output tables and logic for building an MSP package. - /// - public class Patch - { - private List inspectorExtensions; - private Output patch; - private TableDefinitionCollection tableDefinitions; - - public Output PatchOutput - { - get { return this.patch; } - } - - public Patch() - { - this.inspectorExtensions = new List(); - this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions()); - } - - /// - /// Adds an extension. - /// - /// The extension to add. - public void AddExtension(IInspectorExtension extension) - { - this.inspectorExtensions.Add(extension); - } - - public void Load(string patchPath) - { - this.patch = Output.Load(patchPath, false); - } - - /// - /// Include transforms in a patch. - /// - /// List of transforms to attach. - [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] - public void AttachTransforms(List transforms) - { - InspectorCore inspectorCore = new InspectorCore(); - - // Track if at least one transform gets attached. - bool attachedTransform = false; - - if (transforms == null || transforms.Count == 0) - { - throw new WixException(WixErrors.PatchWithoutTransforms()); - } - - // Get the patch id from the WixPatchId table. - string patchId = null; - string clientPatchId = null; - Table wixPatchIdTable = this.patch.Tables["WixPatchId"]; - if (null != wixPatchIdTable && 0 < wixPatchIdTable.Rows.Count) - { - Row patchIdRow = wixPatchIdTable.Rows[0]; - if (null != patchIdRow) - { - patchId = patchIdRow[0].ToString(); - clientPatchId = patchIdRow[1].ToString(); - } - } - - if (null == patchId) - { - throw new WixException(WixErrors.ExpectedPatchIdInWixMsp()); - } - if (null == clientPatchId) - { - throw new WixException(WixErrors.ExpectedClientPatchIdInWixMsp()); - } - - // enumerate patch.Media to map diskId to Media row - Table patchMediaTable = patch.Tables["Media"]; - - if (null == patchMediaTable || patchMediaTable.Rows.Count == 0) - { - throw new WixException(WixErrors.ExpectedMediaRowsInWixMsp()); - } - - Hashtable mediaRows = new Hashtable(patchMediaTable.Rows.Count); - foreach (MediaRow row in patchMediaTable.Rows) - { - int media = row.DiskId; - mediaRows[media] = row; - } - - // enumerate patch.WixPatchBaseline to map baseline to diskId - Table patchBaselineTable = patch.Tables["WixPatchBaseline"]; - - int numPatchBaselineRows = (null != patchBaselineTable) ? patchBaselineTable.Rows.Count : 0; - - Hashtable baselineMedia = new Hashtable(numPatchBaselineRows); - if (patchBaselineTable != null) - { - foreach (Row row in patchBaselineTable.Rows) - { - string baseline = (string)row[0]; - int media = (int)row[1]; - int validationFlags = (int)row[2]; - if (baselineMedia.Contains(baseline)) - { - this.OnMessage(WixErrors.SamePatchBaselineId(row.SourceLineNumbers, baseline)); - } - baselineMedia[baseline] = new int[] { media, validationFlags }; - } - } - - // populate MSP summary information - Table patchSummaryInfo = patch.EnsureTable(this.tableDefinitions["_SummaryInformation"]); - - // Remove properties that will be calculated or are reserved. - for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) - { - Row row = patchSummaryInfo.Rows[i]; - switch ((SummaryInformation.Patch)row[0]) - { - case SummaryInformation.Patch.ProductCodes: - case SummaryInformation.Patch.TransformNames: - case SummaryInformation.Patch.PatchCode: - case SummaryInformation.Patch.InstallerRequirement: - case SummaryInformation.Patch.Reserved11: - case SummaryInformation.Patch.Reserved14: - case SummaryInformation.Patch.Reserved16: - patchSummaryInfo.Rows.RemoveAt(i); - break; - } - } - - // Index remaining summary properties. - SummaryInfoRowCollection summaryInfo = new SummaryInfoRowCollection(patchSummaryInfo); - - // PID_CODEPAGE - if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage)) - { - // set the code page by default to the same code page for the - // string pool in the database. - Row codePage = patchSummaryInfo.CreateRow(null); - codePage[0] = (int)SummaryInformation.Patch.CodePage; - codePage[1] = this.patch.Codepage.ToString(CultureInfo.InvariantCulture); - } - - // GUID patch code for the patch. - Row revisionRow = patchSummaryInfo.CreateRow(null); - revisionRow[0] = (int)SummaryInformation.Patch.PatchCode; - revisionRow[1] = patchId; - - // Indicates the minimum Windows Installer version that is required to install the patch. - Row wordsRow = patchSummaryInfo.CreateRow(null); - wordsRow[0] = (int)SummaryInformation.Patch.InstallerRequirement; - wordsRow[1] = ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture); - - if (!summaryInfo.Contains((int)SummaryInformation.Patch.Security)) - { - Row security = patchSummaryInfo.CreateRow(null); - security[0] = (int)SummaryInformation.Patch.Security; - security[1] = "4"; // Read-only enforced - } - - // use authored comments or default to DisplayName (required) - string comments = null; - - Table msiPatchMetadataTable = patch.Tables["MsiPatchMetadata"]; - Hashtable metadataTable = new Hashtable(); - if (null != msiPatchMetadataTable) - { - foreach (Row row in msiPatchMetadataTable.Rows) - { - metadataTable.Add(row.Fields[1].Data.ToString(), row.Fields[2].Data.ToString()); - } - - if (!summaryInfo.Contains((int)SummaryInformation.Patch.Title) && metadataTable.Contains("DisplayName")) - { - string displayName = (string)metadataTable["DisplayName"]; - - Row title = patchSummaryInfo.CreateRow(null); - title[0] = (int)SummaryInformation.Patch.Title; - title[1] = displayName; - - // default comments use DisplayName as-is (no loc) - comments = displayName; - } - - if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage) && metadataTable.Contains("CodePage")) - { - Row codePage = patchSummaryInfo.CreateRow(null); - codePage[0] = (int)SummaryInformation.Patch.CodePage; - codePage[1] = metadataTable["CodePage"]; - } - - if (!summaryInfo.Contains((int)SummaryInformation.Patch.PackageName) && metadataTable.Contains("Description")) - { - Row subject = patchSummaryInfo.CreateRow(null); - subject[0] = (int)SummaryInformation.Patch.PackageName; - subject[1] = metadataTable["Description"]; - } - - if (!summaryInfo.Contains((int)SummaryInformation.Patch.Manufacturer) && metadataTable.Contains("ManufacturerName")) - { - Row author = patchSummaryInfo.CreateRow(null); - author[0] = (int)SummaryInformation.Patch.Manufacturer; - author[1] = metadataTable["ManufacturerName"]; - } - } - - // special metadata marshalled through the build - Table wixPatchMetadataTable = patch.Tables["WixPatchMetadata"]; - Hashtable wixMetadataTable = new Hashtable(); - if (null != wixPatchMetadataTable) - { - foreach (Row row in wixPatchMetadataTable.Rows) - { - wixMetadataTable.Add(row.Fields[0].Data.ToString(), row.Fields[1].Data.ToString()); - } - - if (wixMetadataTable.Contains("Comments")) - { - comments = (string)wixMetadataTable["Comments"]; - } - } - - // write the package comments to summary info - if (!summaryInfo.Contains((int)SummaryInformation.Patch.Comments) && null != comments) - { - Row commentsRow = patchSummaryInfo.CreateRow(null); - commentsRow[0] = (int)SummaryInformation.Patch.Comments; - commentsRow[1] = comments; - } - - // enumerate transforms - Dictionary productCodes = new Dictionary(); - ArrayList transformNames = new ArrayList(); - ArrayList validTransform = new ArrayList(); - int transformCount = 0; - foreach (PatchTransform mainTransform in transforms) - { - string baseline = null; - int media = -1; - int validationFlags = 0; - - if (baselineMedia.Contains(mainTransform.Baseline)) - { - int[] baselineData = (int[])baselineMedia[mainTransform.Baseline]; - int newMedia = baselineData[0]; - if (media != -1 && media != newMedia) - { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_TransformAuthoredIntoMultipleMedia, media, newMedia)); - } - baseline = mainTransform.Baseline; - media = newMedia; - validationFlags = baselineData[1]; - } - - if (media == -1) - { - // transform's baseline not attached to any Media - continue; - } - - Table patchRefTable = patch.Tables["WixPatchRef"]; - if (patchRefTable != null && patchRefTable.Rows.Count > 0) - { - if (!Patch.ReduceTransform(mainTransform.Transform, patchRefTable)) - { - // transform has none of the content authored into this patch - continue; - } - } - - // Validate the transform doesn't break any patch specific rules. - mainTransform.Validate(); - - // ensure consistent File.Sequence within each Media - MediaRow mediaRow = (MediaRow)mediaRows[media]; - - // Ensure that files are sequenced after the last file in any transform. - Table transformMediaTable = mainTransform.Transform.Tables["Media"]; - if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) - { - foreach (MediaRow transformMediaRow in transformMediaTable.Rows) - { - if (mediaRow.LastSequence < transformMediaRow.LastSequence) - { - // The Binder will pre-increment the sequence. - mediaRow.LastSequence = transformMediaRow.LastSequence; - } - } - } - - // Use the Media/@DiskId if greater for backward compatibility. - if (mediaRow.LastSequence < mediaRow.DiskId) - { - mediaRow.LastSequence = mediaRow.DiskId; - } - - // ignore media table from transform. - mainTransform.Transform.Tables.Remove("Media"); - mainTransform.Transform.Tables.Remove("WixMedia"); - mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); - - string productCode; - Output pairedTransform = this.BuildPairedTransform(patchId, clientPatchId, mainTransform.Transform, mediaRow, validationFlags, out productCode); - productCodes[productCode] = null; - DictionaryEntry entry = new DictionaryEntry(); - entry.Key = productCode; - entry.Value = mainTransform.Transform; - validTransform.Add(entry); - - // attach these transforms to the patch object - // TODO: is this an acceptable way to auto-generate transform stream names? - string transformName = baseline + "." + (++transformCount).ToString(CultureInfo.InvariantCulture); - patch.SubStorages.Add(new SubStorage(transformName, mainTransform.Transform)); - patch.SubStorages.Add(new SubStorage("#" + transformName, pairedTransform)); - transformNames.Add(":" + transformName); - transformNames.Add(":#" + transformName); - attachedTransform = true; - } - - if (!attachedTransform) - { - throw new WixException(WixErrors.PatchWithoutValidTransforms()); - } - - // Validate that a patch authored as removable is actually removable - if (metadataTable.Contains("AllowRemoval")) - { - if ("1" == metadataTable["AllowRemoval"].ToString()) - { - ArrayList tables = Patch.GetPatchUninstallBreakingTables(); - bool result = true; - foreach (DictionaryEntry entry in validTransform) - { - result &= this.CheckUninstallableTransform(entry.Key.ToString(), (Output)entry.Value, tables); - } - - if (!result) - { - throw new WixException(WixErrors.PatchNotRemovable()); - } - } - } - - // Finish filling tables with transform-dependent data. - // Semicolon delimited list of the product codes that can accept the patch. - Table wixPatchTargetTable = patch.Tables["WixPatchTarget"]; - if (null != wixPatchTargetTable) - { - Dictionary targets = new Dictionary(); - bool replace = true; - foreach (Row wixPatchTargetRow in wixPatchTargetTable.Rows) - { - string target = wixPatchTargetRow[0].ToString(); - if (0 == String.CompareOrdinal("*", target)) - { - replace = false; - } - else - { - targets[target] = null; - } - } - - // Replace the target ProductCodes with the authored list. - if (replace) - { - productCodes = targets; - } - else - { - // Copy the authored target ProductCodes into the list. - foreach (string target in targets.Keys) - { - productCodes[target] = null; - } - } - } - - string[] uniqueProductCodes = new string[productCodes.Keys.Count]; - productCodes.Keys.CopyTo(uniqueProductCodes, 0); - - Row templateRow = patchSummaryInfo.CreateRow(null); - templateRow[0] = (int)SummaryInformation.Patch.ProductCodes; - templateRow[1] = String.Join(";", uniqueProductCodes); - - // Semicolon delimited list of transform substorage names in the order they are applied. - Row savedbyRow = patchSummaryInfo.CreateRow(null); - savedbyRow[0] = (int)SummaryInformation.Patch.TransformNames; - savedbyRow[1] = String.Join(";", (string[])transformNames.ToArray(typeof(string))); - - // inspect the patch and filtered transforms - foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) - { - inspectorExtension.Core = inspectorCore; - inspectorExtension.InspectOutput(this.patch); - - // reset - inspectorExtension.Core = null; - } - } - - /// - /// Ensure transform is uninstallable. - /// - /// Product code in transform. - /// Transform generated by torch. - /// Tables to be checked - /// True if the transform is uninstallable - private bool CheckUninstallableTransform(string productCode, Output transform, ArrayList tables) - { - bool ret = true; - foreach (string table in tables) - { - Table wixTable = transform.Tables[table]; - if (null != wixTable) - { - foreach (Row row in wixTable.Rows) - { - if (row.Operation == RowOperation.Add) - { - ret = false; - string primaryKey = row.GetPrimaryKey('/'); - if (null == primaryKey) - { - primaryKey = string.Empty; - } - this.OnMessage(WixErrors.NewRowAddedInTable(row.SourceLineNumbers, productCode, wixTable.Name, primaryKey)); - } - } - } - } - - return ret; - } - - /// - /// Tables affect patch uninstall. - /// - /// list of tables to be checked - private static ArrayList GetPatchUninstallBreakingTables() - { - ArrayList tables = new ArrayList(); - tables.Add("AppId"); - tables.Add("BindImage"); - tables.Add("Class"); - tables.Add("Complus"); - tables.Add("CreateFolder"); - tables.Add("DuplicateFile"); - tables.Add("Environment"); - tables.Add("Extension"); - tables.Add("Font"); - tables.Add("IniFile"); - tables.Add("IsolatedComponent"); - tables.Add("LockPermissions"); - tables.Add("MIME"); - tables.Add("MoveFile"); - tables.Add("MsiLockPermissionsEx"); - tables.Add("MsiServiceConfig"); - tables.Add("MsiServiceConfigFailureActions"); - tables.Add("ODBCAttribute"); - tables.Add("ODBCDataSource"); - tables.Add("ODBCDriver"); - tables.Add("ODBCSourceAttribute"); - tables.Add("ODBCTranslator"); - tables.Add("ProgId"); - tables.Add("PublishComponent"); - tables.Add("RemoveIniFile"); - tables.Add("SelfReg"); - tables.Add("ServiceControl"); - tables.Add("ServiceInstall"); - tables.Add("TypeLib"); - tables.Add("Verb"); - - return tables; - } - - /// - /// Reduce the transform according to the patch references. - /// - /// transform generated by torch. - /// Table contains patch family filter. - /// true if the transform is not empty - public static bool ReduceTransform(Output transform, Table patchRefTable) - { - // identify sections to keep - Hashtable oldSections = new Hashtable(patchRefTable.Rows.Count); - Hashtable newSections = new Hashtable(patchRefTable.Rows.Count); - Hashtable tableKeyRows = new Hashtable(); - ArrayList sequenceList = new ArrayList(); - Hashtable componentFeatureAddsIndex = new Hashtable(); - Hashtable customActionTable = new Hashtable(); - Hashtable directoryTableAdds = new Hashtable(); - Hashtable featureTableAdds = new Hashtable(); - Hashtable keptComponents = new Hashtable(); - Hashtable keptDirectories = new Hashtable(); - Hashtable keptFeatures = new Hashtable(); - Hashtable keptLockPermissions = new Hashtable(); - Hashtable keptMsiLockPermissionExs = new Hashtable(); - - Dictionary> componentCreateFolderIndex = new Dictionary>(); - Dictionary> directoryLockPermissionsIndex = new Dictionary>(); - Dictionary> directoryMsiLockPermissionsExIndex = new Dictionary>(); - - foreach (Row patchRefRow in patchRefTable.Rows) - { - string tableName = (string)patchRefRow[0]; - string key = (string)patchRefRow[1]; - - // Short circuit filtering if all changes should be included. - if ("*" == tableName && "*" == key) - { - Patch.RemoveProductCodeFromTransform(transform); - return true; - } - - Table table = transform.Tables[tableName]; - if (table == null) - { - // table not found - continue; - } - - // index this table - if (!tableKeyRows.Contains(tableName)) - { - Hashtable newKeyRows = new Hashtable(); - foreach (Row newRow in table.Rows) - { - newKeyRows[newRow.GetPrimaryKey('/')] = newRow; - } - tableKeyRows[tableName] = newKeyRows; - } - Hashtable keyRows = (Hashtable)tableKeyRows[tableName]; - - Row row = (Row)keyRows[key]; - if (row == null) - { - // row not found - continue; - } - - // Differ.sectionDelimiter - string[] sections = row.SectionId.Split('/'); - oldSections[sections[0]] = row; - newSections[sections[1]] = row; - } - - // throw away sections not referenced - int keptRows = 0; - Table directoryTable = null; - Table featureTable = null; - Table lockPermissionsTable = null; - Table msiLockPermissionsTable = null; - - foreach (Table table in transform.Tables) - { - if ("_SummaryInformation" == table.Name) - { - continue; - } - - if (table.Name == "AdminExecuteSequence" - || table.Name == "AdminUISequence" - || table.Name == "AdvtExecuteSequence" - || table.Name == "InstallUISequence" - || table.Name == "InstallExecuteSequence") - { - sequenceList.Add(table); - continue; - } - - for (int i = 0; i < table.Rows.Count; i++) - { - Row row = table.Rows[i]; - - if (table.Name == "CreateFolder") - { - string createFolderComponentId = (string)row[1]; - - List directoryList; - if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out directoryList)) - { - directoryList = new List(); - componentCreateFolderIndex.Add(createFolderComponentId, directoryList); - } - - directoryList.Add((string)row[0]); - } - - if (table.Name == "CustomAction") - { - customActionTable.Add(row[0], row); - } - - if (table.Name == "Directory") - { - directoryTable = table; - if (RowOperation.Add == row.Operation) - { - directoryTableAdds.Add(row[0], row); - } - } - - if (table.Name == "Feature") - { - featureTable = table; - if (RowOperation.Add == row.Operation) - { - featureTableAdds.Add(row[0], row); - } - } - - if (table.Name == "FeatureComponents") - { - if (RowOperation.Add == row.Operation) - { - string featureId = (string)row[0]; - string componentId = (string)row[1]; - - if (componentFeatureAddsIndex.ContainsKey(componentId)) - { - ArrayList featureList = (ArrayList)componentFeatureAddsIndex[componentId]; - featureList.Add(featureId); - } - else - { - ArrayList featureList = new ArrayList(); - componentFeatureAddsIndex.Add(componentId, featureList); - featureList.Add(featureId); - } - } - } - - if (table.Name == "LockPermissions") - { - lockPermissionsTable = table; - if ("CreateFolder" == (string)row[1]) - { - string directoryId = (string)row[0]; - - List rowList; - if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out rowList)) - { - rowList = new List(); - directoryLockPermissionsIndex.Add(directoryId, rowList); - } - - rowList.Add(row); - } - } - - if (table.Name == "MsiLockPermissionsEx") - { - msiLockPermissionsTable = table; - if ("CreateFolder" == (string)row[1]) - { - string directoryId = (string)row[0]; - - List rowList; - if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out rowList)) - { - rowList = new List(); - directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); - } - - rowList.Add(row); - } - } - - if (null == row.SectionId) - { - table.Rows.RemoveAt(i); - i--; - } - else - { - string[] sections = row.SectionId.Split('/'); - // ignore the row without section id. - if (0 == sections[0].Length && 0 == sections[1].Length) - { - table.Rows.RemoveAt(i); - i--; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - if ("Component" == table.Name) - { - keptComponents.Add((string)row[0], row); - } - - if ("Directory" == table.Name) - { - keptDirectories.Add(row[0], row); - } - - if ("Feature" == table.Name) - { - keptFeatures.Add(row[0], row); - } - - keptRows++; - } - else - { - table.Rows.RemoveAt(i); - i--; - } - } - } - } - - keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); - - if (null != directoryTable) - { - foreach (Row componentRow in keptComponents.Values) - { - string componentId = (string)componentRow[0]; - - if (RowOperation.Add == componentRow.Operation) - { - // make sure each added component has its required directory and feature heirarchy. - string directoryId = (string)componentRow[2]; - while (null != directoryId && directoryTableAdds.ContainsKey(directoryId)) - { - Row directoryRow = (Row)directoryTableAdds[directoryId]; - - if (!keptDirectories.ContainsKey(directoryId)) - { - directoryTable.Rows.Add(directoryRow); - keptDirectories.Add(directoryRow[0], null); - keptRows++; - } - - directoryId = (string)directoryRow[1]; - } - - if (componentFeatureAddsIndex.ContainsKey(componentId)) - { - foreach (string featureId in (ArrayList)componentFeatureAddsIndex[componentId]) - { - string currentFeatureId = featureId; - while (null != currentFeatureId && featureTableAdds.ContainsKey(currentFeatureId)) - { - Row featureRow = (Row)featureTableAdds[currentFeatureId]; - - if (!keptFeatures.ContainsKey(currentFeatureId)) - { - featureTable.Rows.Add(featureRow); - keptFeatures.Add(featureRow[0], null); - keptRows++; - } - - currentFeatureId = (string)featureRow[1]; - } - } - } - } - - // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. - foreach (string keptComponentId in keptComponents.Keys) - { - List directoryList; - if (componentCreateFolderIndex.TryGetValue(keptComponentId, out directoryList)) - { - foreach (string directoryId in directoryList) - { - List lockPermissionsRowList; - if (directoryLockPermissionsIndex.TryGetValue(directoryId, out lockPermissionsRowList)) - { - foreach (Row lockPermissionsRow in lockPermissionsRowList) - { - string key = lockPermissionsRow.GetPrimaryKey('/'); - if (!keptLockPermissions.ContainsKey(key)) - { - lockPermissionsTable.Rows.Add(lockPermissionsRow); - keptLockPermissions.Add(key, null); - keptRows++; - } - } - } - - List msiLockPermissionsExRowList; - if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out msiLockPermissionsExRowList)) - { - foreach (Row msiLockPermissionsExRow in msiLockPermissionsExRowList) - { - string key = msiLockPermissionsExRow.GetPrimaryKey('/'); - if (!keptMsiLockPermissionExs.ContainsKey(key)) - { - msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); - keptMsiLockPermissionExs.Add(key, null); - keptRows++; - } - } - } - } - } - } - } - } - - keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); - - // Delete tables that are empty. - ArrayList tablesToDelete = new ArrayList(); - foreach (Table table in transform.Tables) - { - if (0 == table.Rows.Count) - { - tablesToDelete.Add(table.Name); - } - } - - // delete separately to avoid messing up enumeration - foreach (string tableName in tablesToDelete) - { - transform.Tables.Remove(tableName); - } - - return keptRows > 0; - } - - /// - /// Remove the ProductCode property from the transform. - /// - /// The transform. - /// - /// Changing the ProductCode is not supported in a patch. - /// - private static void RemoveProductCodeFromTransform(Output transform) - { - Table propertyTable = transform.Tables["Property"]; - if (null != propertyTable) - { - for (int i = 0; i < propertyTable.Rows.Count; ++i) - { - Row propertyRow = propertyTable.Rows[i]; - string property = (string)propertyRow[0]; - - if ("ProductCode" == property) - { - propertyTable.Rows.RemoveAt(i); - break; - } - } - } - } - - /// - /// Check if the section is in a PatchFamily. - /// - /// Section id in target wixout - /// Section id in upgrade wixout - /// Hashtable contains section id should be kept in the baseline wixout. - /// Hashtable contains section id should be kept in the upgrade wixout. - /// true if section in patch family - private static bool IsInPatchFamily(string oldSection, string newSection, Hashtable oldSections, Hashtable newSections) - { - bool result = false; - - if ((String.IsNullOrEmpty(oldSection) && newSections.Contains(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.Contains(oldSection))) - { - result = true; - } - else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.Contains(oldSection) || newSections.Contains(newSection))) - { - result = true; - } - - return result; - } - - /// - /// Reduce the transform sequence tables. - /// - /// ArrayList of tables to be reduced - /// Hashtable contains section id should be kept in the baseline wixout. - /// Hashtable contains section id should be kept in the target wixout. - /// Hashtable contains all the rows in the CustomAction table. - /// Number of rows left - private static int ReduceTransformSequenceTable(ArrayList sequenceList, Hashtable oldSections, Hashtable newSections, Hashtable customAction) - { - int keptRows = 0; - - foreach (Table currentTable in sequenceList) - { - for (int i = 0; i < currentTable.Rows.Count; i++) - { - Row row = currentTable.Rows[i]; - string actionName = row.Fields[0].Data.ToString(); - string[] sections = row.SectionId.Split('/'); - bool isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); - - if (row.Operation == RowOperation.None) - { - // ignore the rows without section id. - if (isSectionIdEmpty) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - currentTable.Rows.RemoveAt(i); - i--; - } - } - else if (row.Operation == RowOperation.Modify) - { - bool sequenceChanged = row.Fields[2].Modified; - bool conditionChanged = row.Fields[1].Modified; - - if (sequenceChanged && !conditionChanged) - { - keptRows++; - } - else if (!sequenceChanged && conditionChanged) - { - if (isSectionIdEmpty) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - currentTable.Rows.RemoveAt(i); - i--; - } - } - else if (sequenceChanged && conditionChanged) - { - if (isSectionIdEmpty) - { - row.Fields[1].Modified = false; - keptRows++; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - row.Fields[1].Modified = false; - keptRows++; - } - } - } - else if (row.Operation == RowOperation.Delete) - { - if (isSectionIdEmpty) - { - // it is a stardard action which is added by wix, we should keep this action. - row.Operation = RowOperation.None; - keptRows++; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - if (customAction.ContainsKey(actionName)) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else - { - // it is a stardard action, we should keep this action. - row.Operation = RowOperation.None; - keptRows++; - } - } - } - else if (row.Operation == RowOperation.Add) - { - if (isSectionIdEmpty) - { - keptRows++; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - if (customAction.ContainsKey(actionName)) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else - { - keptRows++; - } - } - } - } - } - - return keptRows; - } - - /// - /// Create the #transform for the given main transform. - /// - /// Patch GUID from patch authoring. - /// Easily referenced identity for this patch. - /// Transform generated by torch. - /// Media authored into patch. - /// Transform validation flags for the summary information stream. - /// Output string to receive ProductCode. - [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] - public Output BuildPairedTransform(string patchId, string clientPatchId, Output mainTransform, MediaRow mediaRow, int validationFlags, out string productCode) - { - productCode = null; - Output pairedTransform = new Output(null); - pairedTransform.Type = OutputType.Transform; - pairedTransform.Codepage = mainTransform.Codepage; - - // lookup productVersion property to correct summaryInformation - string newProductVersion = null; - Table mainPropertyTable = mainTransform.Tables["Property"]; - if (null != mainPropertyTable) - { - foreach (Row row in mainPropertyTable.Rows) - { - if ("ProductVersion" == (string)row[0]) - { - newProductVersion = (string)row[1]; - } - } - } - - // TODO: build class for manipulating SummaryInformation table - Table mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; - // add required properties - Hashtable mainSummaryRows = new Hashtable(); - foreach (Row mainSummaryRow in mainSummaryTable.Rows) - { - mainSummaryRows[mainSummaryRow[0]] = mainSummaryRow; - } - if (!mainSummaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) - { - Row mainSummaryRow = mainSummaryTable.CreateRow(null); - mainSummaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - mainSummaryRow[1] = validationFlags.ToString(CultureInfo.InvariantCulture); - } - - // copy summary information from core transform - Table pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); - foreach (Row mainSummaryRow in mainSummaryTable.Rows) - { - string value = (string)mainSummaryRow[1]; - switch ((SummaryInformation.Transform)mainSummaryRow[0]) - { - case SummaryInformation.Transform.ProductCodes: - string[] propertyData = value.Split(';'); - string oldProductVersion = propertyData[0].Substring(38); - string upgradeCode = propertyData[2]; - productCode = propertyData[0].Substring(0, 38); - if (newProductVersion == null) - { - newProductVersion = oldProductVersion; - } - - // force mainTranform to old;new;upgrade and pairedTransform to new;new;upgrade - mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); - value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); - break; - case SummaryInformation.Transform.ValidationFlags: - // use validation flags authored into the patch XML - mainSummaryRow[1] = value = validationFlags.ToString(CultureInfo.InvariantCulture); - break; - } - Row pairedSummaryRow = pairedSummaryTable.CreateRow(null); - pairedSummaryRow[0] = mainSummaryRow[0]; - pairedSummaryRow[1] = value; - } - - if (productCode == null) - { - throw new InvalidOperationException(WixStrings.EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo); - } - - // copy File table - Table mainFileTable = mainTransform.Tables["File"]; - if (null != mainFileTable && 0 < mainFileTable.Rows.Count) - { - // We require file source information. - Table mainWixFileTable = mainTransform.Tables["WixFile"]; - if (null == mainWixFileTable) - { - throw new WixException(WixErrors.AdminImageRequired(productCode)); - } - - RowDictionary mainFileRows = new RowDictionary(mainFileTable); - - Table pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); - foreach (WixFileRow mainWixFileRow in mainWixFileTable.Rows) - { - FileRow mainFileRow = mainFileRows[mainWixFileRow.File]; - - // set File.Sequence to non null to satisfy transform bind - mainFileRow.Sequence = 1; - - // delete's don't need rows in the paired transform - if (mainFileRow.Operation == RowOperation.Delete) - { - continue; - } - - FileRow pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); - pairedFileRow.Operation = RowOperation.Modify; - for (int i = 0; i < mainFileRow.Fields.Length; i++) - { - pairedFileRow[i] = mainFileRow[i]; - } - - // override authored media for patch bind - mainWixFileRow.DiskId = mediaRow.DiskId; - - // suppress any change to File.Sequence to avoid bloat - mainFileRow.Fields[7].Modified = false; - - // force File row to appear in the transform - switch (mainFileRow.Operation) - { - case RowOperation.Modify: - case RowOperation.Add: - // set msidbFileAttributesPatchAdded - pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = mainFileRow.Operation; - break; - default: - pairedFileRow.Fields[6].Modified = false; - break; - } - } - } - - // add Media row to pairedTransform - Table pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); - Row pairedMediaRow = pairedMediaTable.CreateRow(null); - pairedMediaRow.Operation = RowOperation.Add; - for (int i = 0; i < mediaRow.Fields.Length; i++) - { - pairedMediaRow[i] = mediaRow[i]; - } - - // add PatchPackage for this Media - Table pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); - pairedPackageTable.Operation = TableOperation.Add; - Row pairedPackageRow = pairedPackageTable.CreateRow(null); - pairedPackageRow.Operation = RowOperation.Add; - pairedPackageRow[0] = patchId; - pairedPackageRow[1] = mediaRow.DiskId; - - // add property to both identify client patches and whether those patches are removable or not - int allowRemoval = 0; - Table msiPatchMetadataTable = this.patch.Tables["MsiPatchMetadata"]; - if (null != msiPatchMetadataTable) - { - foreach (Row msiPatchMetadataRow in msiPatchMetadataTable.Rows) - { - // get the value of the standard AllowRemoval property, if present - string company = (string)msiPatchMetadataRow[0]; - if ((null == company || 0 == company.Length) && "AllowRemoval" == (string)msiPatchMetadataRow[1]) - { - allowRemoval = Int32.Parse((string)msiPatchMetadataRow[2], CultureInfo.InvariantCulture); - } - } - } - - // add the property to the patch transform's Property table - Table pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); - pairedPropertyTable.Operation = TableOperation.Add; - Row pairedPropertyRow = pairedPropertyTable.CreateRow(null); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = string.Concat(clientPatchId, ".AllowRemoval"); - pairedPropertyRow[1] = allowRemoval.ToString(CultureInfo.InvariantCulture); - - // add this patch code GUID to the patch transform to identify - // which patches are installed, including in multi-patch - // installations. - pairedPropertyRow = pairedPropertyTable.CreateRow(null); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = string.Concat(clientPatchId, ".PatchCode"); - pairedPropertyRow[1] = patchId; - - // add PATCHNEWPACKAGECODE to apply to admin layouts - pairedPropertyRow = pairedPropertyTable.CreateRow(null); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; - pairedPropertyRow[1] = patchId; - - // add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts - Table _summaryInformationTable = this.patch.Tables["_SummaryInformation"]; - if (null != _summaryInformationTable) - { - foreach (Row row in _summaryInformationTable.Rows) - { - if (3 == (int)row[0]) // PID_SUBJECT - { - pairedPropertyRow = pairedPropertyTable.CreateRow(null); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; - pairedPropertyRow[1] = row[1]; - } - else if (6 == (int)row[0]) // PID_COMMENTS - { - pairedPropertyRow = pairedPropertyTable.CreateRow(null); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; - pairedPropertyRow[1] = row[1]; - } - } - } - - return pairedTransform; - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs mea) - { - Messaging.Instance.OnMessage(mea); - } - } -} diff --git a/src/WixToolset.Core/PatchAPI/PatchInterop.cs b/src/WixToolset.Core/PatchAPI/PatchInterop.cs deleted file mode 100644 index ce749a33..00000000 --- a/src/WixToolset.Core/PatchAPI/PatchInterop.cs +++ /dev/null @@ -1,1002 +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.PatchAPI -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.Runtime.InteropServices; - - /// - /// Interop class for the mspatchc.dll. - /// - internal static class PatchInterop - { - // From WinError.h in the Platform SDK - internal const ushort FACILITY_WIN32 = 7; - - /// - /// Parse a number from text in either hex or decimal. - /// - /// Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise. - /// Numeric value that source represents. - static internal UInt32 ParseHexOrDecimal(string source) - { - string value = source.Trim(); - if (String.Equals(value.Substring(0,2), "0x", StringComparison.OrdinalIgnoreCase)) - { - return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); - } - else - { - return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat); - } - } - - /// - /// Create a binary delta file. - /// - /// Name of the delta file to create. - /// Name of updated file. - /// Optional paths to updated file's symbols. - /// Optional offsets to the delta retain sections in the updated file. - /// Optional array of target files. - /// Optional array of target files' symbol paths (must match basisFiles array). - /// Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries). - /// Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries). - /// Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries). - /// Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries). - /// ApiPatchingSymbolFlags value. - /// OptimizePatchSizeForLargeFiles value. - /// Flag to indicate retain ranges were ignored due to mismatch. - /// true if delta file was created, false if whole file should be used instead. - static public bool CreateDelta( - string deltaFile, - string targetFile, - string targetSymbolPath, - string targetRetainOffsets, - string[] basisFiles, - string[] basisSymbolPaths, - string[] basisIgnoreLengths, - string[] basisIgnoreOffsets, - string[] basisRetainLengths, - string[] basisRetainOffsets, - PatchSymbolFlagsType apiPatchingSymbolFlags, - bool optimizePatchSizeForLargeFiles, - out bool retainRangesIgnored - ) - { - retainRangesIgnored = false; - if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP | PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES | PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO))) - { - throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags"); - } - - if (null == deltaFile || 0 == deltaFile.Length) - { - throw new ArgumentNullException("deltaFile"); - } - - if (null == targetFile || 0 == targetFile.Length) - { - throw new ArgumentNullException("targetFile"); - } - - if (null == basisFiles || 0 == basisFiles.Length) - { - return false; - } - uint countOldFiles = (uint) basisFiles.Length; - - if (null != basisSymbolPaths) - { - if (0 != basisSymbolPaths.Length) - { - if ((uint) basisSymbolPaths.Length != countOldFiles) - { - throw new ArgumentOutOfRangeException("basisSymbolPaths"); - } - } - } - // a null basisSymbolPaths is allowed. - - if (null != basisIgnoreLengths) - { - if (0 != basisIgnoreLengths.Length) - { - if ((uint) basisIgnoreLengths.Length != countOldFiles) - { - throw new ArgumentOutOfRangeException("basisIgnoreLengths"); - } - } - } - else - { - basisIgnoreLengths = new string[countOldFiles]; - } - - if (null != basisIgnoreOffsets) - { - if (0 != basisIgnoreOffsets.Length) - { - if ((uint) basisIgnoreOffsets.Length != countOldFiles) - { - throw new ArgumentOutOfRangeException("basisIgnoreOffsets"); - } - } - } - else - { - basisIgnoreOffsets = new string[countOldFiles]; - } - - if (null != basisRetainLengths) - { - if (0 != basisRetainLengths.Length) - { - if ((uint) basisRetainLengths.Length != countOldFiles) - { - throw new ArgumentOutOfRangeException("basisRetainLengths"); - } - } - } - else - { - basisRetainLengths = new string[countOldFiles]; - } - - if (null != basisRetainOffsets) - { - if (0 != basisRetainOffsets.Length) - { - if ((uint) basisRetainOffsets.Length != countOldFiles) - { - throw new ArgumentOutOfRangeException("basisRetainOffsets"); - } - } - } - else - { - basisRetainOffsets = new string[countOldFiles]; - } - - PatchOptionData pod = new PatchOptionData(); - pod.symbolOptionFlags = apiPatchingSymbolFlags; - pod.newFileSymbolPath = targetSymbolPath; - pod.oldFileSymbolPathArray = basisSymbolPaths; - pod.extendedOptionFlags = 0; - PatchOldFileInfoW[] oldFileInfoArray = new PatchOldFileInfoW[countOldFiles]; - string[] newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(',')); - for (uint i = 0; i < countOldFiles; ++i) - { - PatchOldFileInfoW ofi = new PatchOldFileInfoW(); - ofi.oldFileName = basisFiles[i]; - string[] ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(',')); - string[] ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(',')); - string[] retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(',')); - string[] retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(',')); - // Validate inputs - if (ignoreLengthArray.Length != ignoreOffsetArray.Length) - { - throw new ArgumentOutOfRangeException("basisIgnoreLengths"); - } - - if (retainLengthArray.Length != retainOffsetArray.Length) - { - throw new ArgumentOutOfRangeException("basisRetainLengths"); - } - - if (newRetainOffsetArray.Length != retainOffsetArray.Length) - { - // remove all retain range information - retainRangesIgnored = true; - for (uint j = 0; j < countOldFiles; ++j) - { - basisRetainLengths[j] = null; - basisRetainOffsets[j] = null; - } - retainLengthArray = new string[0]; - retainOffsetArray = new string[0]; - newRetainOffsetArray = new string[0]; - for (uint j = 0; j < oldFileInfoArray.Length; ++j) - { - oldFileInfoArray[j].retainRange = null; - } - } - - // Populate IgnoreRange structure - PatchIgnoreRange[] ignoreArray = null; - if (0 != ignoreLengthArray.Length) - { - ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length]; - for (int j = 0; j < ignoreLengthArray.Length; ++j) - { - PatchIgnoreRange ignoreRange = new PatchIgnoreRange(); - ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]); - ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]); - ignoreArray[j] = ignoreRange; - } - ofi.ignoreRange = ignoreArray; - } - - PatchRetainRange[] retainArray = null; - if (0 != newRetainOffsetArray.Length) - { - retainArray = new PatchRetainRange[retainLengthArray.Length]; - for (int j = 0; j < newRetainOffsetArray.Length; ++j) - { - PatchRetainRange retainRange = new PatchRetainRange(); - retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]); - retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]); - retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]); - retainArray[j] = retainRange; - } - ofi.retainRange = retainArray; - } - oldFileInfoArray[i] = ofi; - } - - if (CreatePatchFileExW( - countOldFiles, - oldFileInfoArray, - targetFile, - deltaFile, - PatchOptionFlags(optimizePatchSizeForLargeFiles), - pod, - null, - IntPtr.Zero)) - { - return true; - } - - // determine if this is an error or a need to use whole file. - int err = Marshal.GetLastWin32Error(); - switch(err) - { - case unchecked((int) ERROR_PATCH_BIGGER_THAN_COMPRESSED): - break; - - // too late to exclude this file -- should have been caught before - case unchecked((int) ERROR_PATCH_SAME_FILE): - default: - throw new System.ComponentModel.Win32Exception(err); - } - return false; - } - - /// - /// Extract the delta header. - /// - /// Name of delta file. - /// Name of file to create with the delta's header. - static public void ExtractDeltaHeader(string delta, string deltaHeader) - { - if (!ExtractPatchHeaderToFileW(delta, deltaHeader)) - { - throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); - } - } - - /// - /// Returns the PatchOptionFlags to use. - /// - /// True if optimizing for large files. - /// PATCH_OPTION_FLAG values - static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles) - { - UInt32 flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST; - if (optimizeForLargeFiles) - { - flags |= PATCH_OPTION_USE_LZX_LARGE; - } - return flags; - } - - //--------------------------------------------------------------------- - // From PatchApi.h - //--------------------------------------------------------------------- - - // - // The following contants can be combined and used as the OptionFlags - // parameter in the patch creation apis. - - internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower) - - internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large) - internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal - internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries - internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer) - - internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports - internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks - internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image - internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same - internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower) - internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero - internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps - internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch - internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support) - internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer) - internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally) - - internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007; - - // - // The following flags are used with PATCH_OPTION_DATA SymbolOptionFlags: - // - - [Flags] - public enum PatchSymbolFlagsType :uint - { - PATCH_SYMBOL_NO_IMAGEHLP = 0x00000001, // don't use imagehlp.dll - PATCH_SYMBOL_NO_FAILURES = 0x00000002, // don't fail patch due to imagehlp failures - PATCH_SYMBOL_UNDECORATED_TOO = 0x00000004, // after matching decorated symbols, try to match remaining by undecorated names - PATCH_SYMBOL_RESERVED1 = 0x80000000, // (used internally) - MaxValue = PATCH_SYMBOL_NO_IMAGEHLP | PATCH_SYMBOL_NO_FAILURES | PATCH_SYMBOL_UNDECORATED_TOO - } - - // - // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags: - // - - internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer) - internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer) - - // - // In addition to the standard Win32 error codes, the following error codes may - // be returned via GetLastError() when one of the patch APIs fails. - - internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create - internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create - internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create - internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create - internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create - internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create - - /// - /// Delegate type that the PatchAPI calls for progress notification. - /// - /// . - /// . - /// . - /// True for success - public delegate bool PatchProgressCallback( - IntPtr context, - uint currentPosition, - uint maxPosition - ); - - /// - /// Delegate type that the PatchAPI calls for patch symbol load information. - /// - /// . - /// . - /// . - /// . - /// . - /// . - /// . - /// . - /// ??? - public delegate bool PatchSymloadCallback( - uint whichFile, // 0 for new file, 1 for first old file, etc - [MarshalAs(UnmanagedType.LPStr)] string symbolFileName, - uint symType, // see SYM_TYPE in imagehlp.h - uint symbolFileCheckSum, - uint symbolFileTimeDate, - uint imageFileCheckSum, - uint imageFileTimeDate, - IntPtr context - ); - - /// - /// Wraps PATCH_IGNORE_RANGE - /// - [StructLayout(LayoutKind.Sequential)] - internal class PatchIgnoreRange - { - public uint offsetInOldFile; - public uint lengthInBytes; - } - - /// - /// Wraps PATCH_RETAIN_RANGE - /// - [StructLayout(LayoutKind.Sequential)] - internal class PatchRetainRange - { - public uint offsetInOldFile; - public uint lengthInBytes; - public uint offsetInNewFile; - } - - /// - /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion) - /// - internal class PatchOldFileInfo - { - public PatchIgnoreRange[] ignoreRange; - public PatchRetainRange[] retainRange; - } - - /// - /// Wraps PATCH_OLD_FILE_INFO_W - /// - internal class PatchOldFileInfoW : PatchOldFileInfo - { - public string oldFileName; - } - - /// - /// Wraps each PATCH_INTERLEAVE_MAP Range - /// - [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)] - internal class PatchInterleaveMapRange - { - public uint oldOffset; - public uint oldLength; - public uint newLength; - } - - /// - /// Wraps PATCH_INTERLEAVE_MAP - /// - internal class PatchInterleaveMap - { - public PatchInterleaveMapRange[] ranges = null; - } - - - /// - /// Wraps PATCH_OPTION_DATA - /// - [BestFitMapping(false, ThrowOnUnmappableChar = true)] - internal class PatchOptionData - { - public PatchSymbolFlagsType symbolOptionFlags; // PATCH_SYMBOL_xxx flags - [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode - [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ] - public uint extendedOptionFlags; - public PatchSymloadCallback symLoadCallback = null; - public IntPtr symLoadContext = IntPtr.Zero; - public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer) - public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer) - } - - // - // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode) - // path argument is available, even when used with one of the Unicode APIs - // such as CreatePatchFileW. This is because the unlerlying system services - // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names. - // - - // - // A note about PATCH_RETAIN_RANGE specifiers with multiple old files: - // - // Each old version file must have the same RetainRangeCount, and the same - // retain range LengthInBytes and OffsetInNewFile values in the same order. - // Only the OffsetInOldFile values can differ between old foles for retain - // ranges. - // - - // - // The following prototypes are (some of the) interfaces for creating patches from files. - // - - /// - /// Creates a new delta. - /// - /// Size of oldFileInfoArray. - /// Target file information. - /// Name of updated file. - /// Name of delta to create. - /// PATCH_OPTION_xxx. - /// Optional PATCH_OPTION_DATA structure. - /// Delegate for progress callbacks. - /// Context for progress callback delegate. - /// true if successfull, sets Marshal.GetLastWin32Error() if not. - [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CreatePatchFileExW( - uint oldFileCount, // maximum 255 - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")] - PatchOldFileInfoW[] oldFileInfoArray, - string newFileName, // input file (required) - string patchFileName, // output file (required) - uint optionFlags, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")] - PatchOptionData optionData, - [MarshalAs (UnmanagedType.FunctionPtr)] - PatchProgressCallback progressCallback, - IntPtr context - ); - - /// - /// Extracts delta header from delta. - /// - /// Name of delta file. - /// Name of file to create with delta header. - /// true if successfull, sets Marshal.GetLastWin32Error() if not. - [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ExtractPatchHeaderToFileW( - string patchFileName, // input file - string patchHeaderFileName // output file - ); - - // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks - - /// - /// Marshals arguments for the CreatePatch~ APIs - /// - [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] - internal class PatchAPIMarshaler : ICustomMarshaler - { - internal static ICustomMarshaler GetInstance(string cookie) - { - return new PatchAPIMarshaler(cookie); - } - - private enum MarshalType - { - PATCH_OPTION_DATA, - PATCH_OLD_FILE_INFO_W - }; - private PatchAPIMarshaler.MarshalType marshalType; - - private PatchAPIMarshaler(string cookie) - { - this.marshalType = (PatchAPIMarshaler.MarshalType) Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie); - } - - // - // Summary: - // Returns the size of the native data to be marshaled. - // - // Returns: - // The size in bytes of the native data. - public int GetNativeDataSize() - { - return Marshal.SizeOf(typeof(IntPtr)); - } - - // - // Summary: - // Performs necessary cleanup of the managed data when it is no longer needed. - // - // Parameters: - // ManagedObj: - // The managed object to be destroyed. - public void CleanUpManagedData(object ManagedObj) - { - } - - // - // Summary: - // Performs necessary cleanup of the unmanaged data when it is no longer needed. - // - // Parameters: - // pNativeData: - // A pointer to the unmanaged data to be destroyed. - public void CleanUpNativeData(IntPtr pNativeData) - { - if (IntPtr.Zero == pNativeData) - { - return; - } - - switch (this.marshalType) - { - case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: - this.CleanUpPOD(pNativeData); - break; - default: - this.CleanUpPOFI_A(pNativeData); - break; - } - } - - // - // Summary: - // Converts the managed data to unmanaged data. - // - // Parameters: - // ManagedObj: - // The managed object to be converted. - // - // Returns: - // Returns the COM view of the managed object. - public IntPtr MarshalManagedToNative(object ManagedObj) - { - if (null == ManagedObj) - { - return IntPtr.Zero; - } - - switch(this.marshalType) - { - case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: - return this.MarshalPOD(ManagedObj as PatchOptionData); - case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: - return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]); - default: - throw new InvalidOperationException(); - } - } - - - // - // Summary: - // Converts the unmanaged data to managed data. - // - // Parameters: - // pNativeData: - // A pointer to the unmanaged data to be wrapped. - // - // Returns: - // Returns the managed view of the COM data. - public object MarshalNativeToManaged(IntPtr pNativeData) - { - return null; - } - - // Implementation ************************************************* - - // PATCH_OPTION_DATA offsets - private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32)); - private static readonly int newFileSymbolPathOffset = 2*Marshal.SizeOf(typeof(Int32)); - private static readonly int oldFileSymbolPathArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); - private static readonly int extendedOptionFlagsOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); - private static readonly int symLoadCallbackOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); - private static readonly int symLoadContextOffset = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); - private static readonly int interleaveMapArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 4*Marshal.SizeOf(typeof(IntPtr)); - private static readonly int maxLzxWindowSizeOffset = 3*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); - private static readonly int patchOptionDataSize = 4*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); - - // PATCH_OLD_FILE_INFO offsets - private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32)); - private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); - private static readonly int ignoreRangeArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); - private static readonly int retainRangeCountOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); - private static readonly int retainRangeArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); - private static readonly int patchOldFileInfoSize = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); - - // Methods and data used to preserve data needed for cleanup - - // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount) - private static readonly Dictionary OldFileCounts = new Dictionary(); - private static readonly object OldFileCountsLock = new object(); - - private IntPtr CreateMainStruct(int oldFileCount) - { - int nativeSize; - switch(this.marshalType) - { - case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: - nativeSize = patchOptionDataSize; - break; - case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: - nativeSize = oldFileCount*patchOldFileInfoSize; - break; - default: - throw new InvalidOperationException(); - } - - IntPtr native = Marshal.AllocCoTaskMem(nativeSize); - - lock (PatchAPIMarshaler.OldFileCountsLock) - { - PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount); - } - - return native; - } - - private static void ReleaseMainStruct(IntPtr native) - { - lock (PatchAPIMarshaler.OldFileCountsLock) - { - PatchAPIMarshaler.OldFileCounts.Remove(native); - } - Marshal.FreeCoTaskMem(native); - } - - private static int GetOldFileCount(IntPtr native) - { - lock (PatchAPIMarshaler.OldFileCountsLock) - { - return PatchAPIMarshaler.OldFileCounts[native]; - } - } - - // Helper methods - - private static IntPtr OptionalAnsiString(string managed) - { - return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed); - } - - private static IntPtr OptionalUnicodeString(string managed) - { - return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed); - } - - // string array must be of the same length as the number of old files - private static IntPtr CreateArrayOfStringA(string[] managed) - { - if (null == managed) - { - return IntPtr.Zero; - } - - int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); - IntPtr native = Marshal.AllocCoTaskMem(size); - - for (int i = 0; i < managed.Length; ++i) - { - Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i])); - } - - return native; - } - - // string array must be of the same length as the number of old files - private static IntPtr CreateArrayOfStringW(string[] managed) - { - if (null == managed) - { - return IntPtr.Zero; - } - - int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); - IntPtr native = Marshal.AllocCoTaskMem(size); - - for (int i = 0; i < managed.Length; ++i) - { - Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i])); - } - - return native; - } - - private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed) - { - if (null == managed) - { - return IntPtr.Zero; - } - - if (null == managed.ranges) - { - return IntPtr.Zero; - } - - if (0 == managed.ranges.Length) - { - return IntPtr.Zero; - } - - IntPtr native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32)) - + managed.ranges.Length*(Marshal.SizeOf(typeof(PatchInterleaveMap)))); - WriteUInt32(native, (uint) managed.ranges.Length); - - for (int i = 0; i < managed.ranges.Length; ++i) - { - Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i*Marshal.SizeOf(typeof(PatchInterleaveMap))), false); - } - return native; - } - - private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed) - { - if (null == managed) - { - return IntPtr.Zero; - } - - IntPtr native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr))); - - for (int i = 0; i < managed.Length; ++i) - { - Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i])); - } - - return native; - } - - private static void WriteUInt32(IntPtr native, uint data) - { - Marshal.WriteInt32(native, unchecked((int) data)); - } - - private static void WriteUInt32(IntPtr native, int offset, uint data) - { - Marshal.WriteInt32(native, offset, unchecked((int) data)); - } - - // Marshal operations - - private IntPtr MarshalPOD(PatchOptionData managed) - { - if (null == managed) - { - throw new ArgumentNullException("managed"); - } - - IntPtr native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length); - Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct - WriteUInt32(native, symbolOptionFlagsOffset, (uint) managed.symbolOptionFlags); - Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath)); - Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray)); - WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags); - - // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null. - if (null == managed.symLoadCallback) - { - Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero); - } - else - { - Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback)); - } - - Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext); - Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray)); - WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize); - return native; - } - - private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed) - { - if (null == managed) - { - throw new ArgumentNullException("managed"); - } - - if (0 == managed.Length) - { - return IntPtr.Zero; - } - - IntPtr native = this.CreateMainStruct(managed.Length); - - for (int i = 0; i < managed.Length; ++i) - { - PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize)); - } - - return native; - } - - private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native) - { - PatchAPIMarshaler.MarshalPOFI(managed, native); - Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName - } - - private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native) - { - Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct - WriteUInt32(native, ignoreRangeCountOffset, - (null == managed.ignoreRange) ? 0 : (uint) managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255 - Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray - WriteUInt32(native, retainRangeCountOffset, - (null == managed.retainRange) ? 0 : (uint) managed.retainRange.Length); // RetainRangeCount // maximum 255 - Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray - } - - private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array) - { - if (null == array) - { - return IntPtr.Zero; - } - - if (0 == array.Length) - { - return IntPtr.Zero; - } - - IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchIgnoreRange))); - - for (int i = 0; i < array.Length; ++i) - { - Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchIgnoreRange)))), false); - } - - return native; - } - - private static IntPtr MarshalPRRArray(PatchRetainRange[] array) - { - if (null == array) - { - return IntPtr.Zero; - } - - if (0 == array.Length) - { - return IntPtr.Zero; - } - - IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchRetainRange))); - - for (int i = 0; i < array.Length; ++i) - { - Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchRetainRange)))), false); - } - - return native; - } - - // CleanUp operations - - private void CleanUpPOD(IntPtr native) - { - Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset)); - - if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)) - { - for (int i = 0; i < GetOldFileCount(native); ++i) - { - Marshal.FreeCoTaskMem( - Marshal.ReadIntPtr( - Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset), - i*Marshal.SizeOf(typeof(IntPtr)))); - } - - Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)); - } - - if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset)) - { - for (int i = 0; i < GetOldFileCount(native); ++i) - { - Marshal.FreeCoTaskMem( - Marshal.ReadIntPtr( - Marshal.ReadIntPtr(native, interleaveMapArrayOffset), - i*Marshal.SizeOf(typeof(IntPtr)))); - } - - Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset)); - } - - PatchAPIMarshaler.ReleaseMainStruct(native); - } - - private void CleanUpPOFI_A(IntPtr native) - { - for (int i = 0; i < GetOldFileCount(native); ++i) - { - PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i*patchOldFileInfoSize)); - } - - PatchAPIMarshaler.ReleaseMainStruct(native); - } - - private static void CleanUpPOFI(IntPtr native) - { - if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset)) - { - Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset)); - } - - PatchAPIMarshaler.CleanUpPOFIH(native); - } - - private static void CleanUpPOFIH(IntPtr native) - { - if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)) - { - Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)); - } - - if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset)) - { - Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset)); - } - } - } - } -} diff --git a/src/WixToolset.Core/PatchSymbolFlagsType.cs b/src/WixToolset.Core/PatchSymbolFlagsType.cs new file mode 100644 index 00000000..eeb5c798 --- /dev/null +++ b/src/WixToolset.Core/PatchSymbolFlagsType.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using System; + + // + // The following flags are used with PATCH_OPTION_DATA SymbolOptionFlags: + // + [Flags] + public enum PatchSymbolFlagsType : uint + { + PATCH_SYMBOL_NO_IMAGEHLP = 0x00000001, // don't use imagehlp.dll + PATCH_SYMBOL_NO_FAILURES = 0x00000002, // don't fail patch due to imagehlp failures + PATCH_SYMBOL_UNDECORATED_TOO = 0x00000004, // after matching decorated symbols, try to match remaining by undecorated names + PATCH_SYMBOL_RESERVED1 = 0x80000000, // (used internally) + MaxValue = PATCH_SYMBOL_NO_IMAGEHLP | PATCH_SYMBOL_NO_FAILURES | PATCH_SYMBOL_UNDECORATED_TOO + } +} diff --git a/src/WixToolset.Core/PatchTransform.cs b/src/WixToolset.Core/PatchTransform.cs index c87b1a21..46e4e6d7 100644 --- a/src/WixToolset.Core/PatchTransform.cs +++ b/src/WixToolset.Core/PatchTransform.cs @@ -155,7 +155,7 @@ namespace WixToolset if (!deletedComponent.ContainsKey(componentId)) { bool foundRemoveFileEntry = false; - string filename = Msi.Installer.GetName((string)row[2], false, true); + string filename = Common.GetName((string)row[2], false, true); Table removeFileTable = this.Transform.Tables["RemoveFile"]; if (null != removeFileTable) @@ -172,7 +172,7 @@ namespace WixToolset // Check if there is a RemoveFile entry for this file if (null != removeFileRow[2]) { - string removeFileName = Msi.Installer.GetName((string)removeFileRow[2], false, true); + string removeFileName = Common.GetName((string)removeFileRow[2], false, true); // Convert the MSI format for a wildcard string to Regex format. removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); diff --git a/src/WixToolset.Core/Properties/AssemblyInfo.cs b/src/WixToolset.Core/Properties/AssemblyInfo.cs index b3740b2a..81274e3f 100644 --- a/src/WixToolset.Core/Properties/AssemblyInfo.cs +++ b/src/WixToolset.Core/Properties/AssemblyInfo.cs @@ -5,5 +5,5 @@ using System.Reflection; using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] -[assembly: CLSCompliant(true)] +[assembly: CLSCompliant(false)] [assembly: ComVisible(false)] diff --git a/src/WixToolset.Core/ProvidesDependency.cs b/src/WixToolset.Core/ProvidesDependency.cs deleted file mode 100644 index ea96b5c8..00000000 --- a/src/WixToolset.Core/ProvidesDependency.cs +++ /dev/null @@ -1,108 +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 -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Represents an authored or imported dependency provider. - /// - internal sealed class ProvidesDependency - { - /// - /// Creates a new instance of the class from a . - /// - /// The from which data is imported. - internal ProvidesDependency(Row row) - : this((string)row[2], (string)row[3], (string)row[4], (int?)row[5]) - { - } - - /// - /// Creates a new instance of the class. - /// - /// The unique key of the dependency. - /// Additional attributes for the dependency. - internal ProvidesDependency(string key, string version, string displayName, int? attributes) - { - this.Key = key; - this.Version = version; - this.DisplayName = displayName; - this.Attributes = attributes; - } - - /// - /// Gets or sets the unique key of the package provider. - /// - internal string Key { get; set; } - - /// - /// Gets or sets the version of the package provider. - /// - internal string Version { get; set; } - - /// - /// Gets or sets the display name of the package provider. - /// - internal string DisplayName { get; set; } - - /// - /// Gets or sets the attributes for the dependency. - /// - internal int? Attributes { get; set; } - - /// - /// Gets or sets whether the dependency was imported from the package. - /// - internal bool Imported { get; set; } - - /// - /// Gets whether certain properties are the same. - /// - /// Another to compare. - /// This is not the same as object equality, but only checks a subset of properties - /// to determine if the objects are similar and could be merged into a collection. - /// True if certain properties are the same. - internal bool Equals(ProvidesDependency other) - { - if (null != other) - { - return this.Key == other.Key && - this.Version == other.Version && - this.DisplayName == other.DisplayName; - } - - return false; - } - - /// - /// Writes the dependency to the bundle XML manifest. - /// - /// The for the bundle XML manifest. - internal void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("Provides"); - writer.WriteAttributeString("Key", this.Key); - - if (!String.IsNullOrEmpty(this.Version)) - { - writer.WriteAttributeString("Version", this.Version); - } - - if (!String.IsNullOrEmpty(this.DisplayName)) - { - writer.WriteAttributeString("DisplayName", this.DisplayName); - } - - if (this.Imported) - { - // The package dependency was explicitly authored into the manifest. - writer.WriteAttributeString("Imported", "yes"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core/ProvidesDependencyCollection.cs b/src/WixToolset.Core/ProvidesDependencyCollection.cs deleted file mode 100644 index a777afb0..00000000 --- a/src/WixToolset.Core/ProvidesDependencyCollection.cs +++ /dev/null @@ -1,64 +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 -{ - using System; - using System.Collections.ObjectModel; - - /// - /// A case-insensitive collection of unique objects. - /// - internal sealed class ProvidesDependencyCollection : KeyedCollection - { - /// - /// Creates a case-insensitive collection of unique objects. - /// - internal ProvidesDependencyCollection() - : base(StringComparer.InvariantCultureIgnoreCase) - { - } - - /// - /// Adds the to the collection if it doesn't already exist. - /// - /// The to add to the collection. - /// True if the was added to the collection; otherwise, false. - /// The parameter is null. - internal bool Merge(ProvidesDependency dependency) - { - if (null == dependency) - { - throw new ArgumentNullException("dependency"); - } - - // If the dependency key is already in the collection, verify equality for a subset of properties. - if (this.Contains(dependency.Key)) - { - ProvidesDependency current = this[dependency.Key]; - if (!current.Equals(dependency)) - { - return false; - } - } - - base.Add(dependency); - return true; - } - - /// - /// Gets the for the . - /// - /// The dependency to index. - /// The parameter is null. - /// The for the . - protected override string GetKeyForItem(ProvidesDependency dependency) - { - if (null == dependency) - { - throw new ArgumentNullException("dependency"); - } - - return dependency.Key; - } - } -} diff --git a/src/WixToolset.Core/TransformsFlags.cs b/src/WixToolset.Core/TransformsFlags.cs new file mode 100644 index 00000000..d9ec94ac --- /dev/null +++ b/src/WixToolset.Core/TransformsFlags.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Summary information values for the CharCount property in transforms. + /// + [Flags] + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + public enum TransformFlags + { + /// Ignore error when adding a row that exists. + ErrorAddExistingRow = 0x1, + + /// Ignore error when deleting a row that does not exist. + ErrorDeleteMissingRow = 0x2, + + /// Ignore error when adding a table that exists. + ErrorAddExistingTable = 0x4, + + /// Ignore error when deleting a table that does not exist. + ErrorDeleteMissingTable = 0x8, + + /// Ignore error when updating a row that does not exist. + ErrorUpdateMissingRow = 0x10, + + /// Ignore error when transform and database code pages do not match, and their code pages are neutral. + ErrorChangeCodePage = 0x20, + + /// Default language must match base database. + ValidateLanguage = 0x10000, + + /// Product must match base database. + ValidateProduct = 0x20000, + + /// Check major version only. + ValidateMajorVersion = 0x80000, + + /// Check major and minor versions only. + ValidateMinorVersion = 0x100000, + + /// Check major, minor, and update versions. + ValidateUpdateVersion = 0x200000, + + /// Installed version lt base version. + ValidateNewLessBaseVersion = 0x400000, + + /// Installed version lte base version. + ValidateNewLessEqualBaseVersion = 0x800000, + + /// Installed version eq base version. + ValidateNewEqualBaseVersion = 0x1000000, + + /// Installed version gte base version. + ValidateNewGreaterEqualBaseVersion = 0x2000000, + + /// Installed version gt base version. + ValidateNewGreaterBaseVersion = 0x4000000, + + /// UpgradeCode must match base database. + ValidateUpgradeCode = 0x8000000, + + /// Masks all version checks on ProductVersion. + ProductVersionMask = ValidateMajorVersion | ValidateMinorVersion | ValidateUpdateVersion, + + /// Masks all operations on ProductVersion. + ProductVersionOperatorMask = ValidateNewLessBaseVersion | ValidateNewLessEqualBaseVersion | ValidateNewEqualBaseVersion | ValidateNewGreaterEqualBaseVersion | ValidateNewGreaterBaseVersion, + + /// Default value for instance transforms. + InstanceTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct | ValidateUpdateVersion | ValidateNewGreaterEqualBaseVersion, + + /// Default value for language transforms. + LanguageTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct, + + /// Default value for patch transforms. + PatchTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ValidateProduct | ValidateUpdateVersion | ValidateNewEqualBaseVersion | ValidateUpgradeCode, + } +} diff --git a/src/WixToolset.Core/UnbindContext.cs b/src/WixToolset.Core/UnbindContext.cs new file mode 100644 index 00000000..ed55f312 --- /dev/null +++ b/src/WixToolset.Core/UnbindContext.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class UnbindContext : IUnbindContext + { + public Messaging Messaging { get; } = Messaging.Instance; + + public string ExportBasePath { get; set; } + + public string InputFilePath { get; set; } + + public string IntermediateFolder { get; set; } + + public bool IsAdminImage { get; set; } + + public bool SuppressExtractCabinets { get; set; } + + public bool SuppressDemodularization { get; set; } + } +} diff --git a/src/WixToolset.Core/Unbinder.cs b/src/WixToolset.Core/Unbinder.cs index 744d5536..2ff51997 100644 --- a/src/WixToolset.Core/Unbinder.cs +++ b/src/WixToolset.Core/Unbinder.cs @@ -1,37 +1,18 @@ // 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 +namespace WixToolset.Core { - using System; - using System.CodeDom.Compiler; using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Globalization; using System.IO; - using System.Linq; - using System.Text.RegularExpressions; - using WixToolset.Bind; - using WixToolset.Bind.Bundles; - using WixToolset.Cab; using WixToolset.Data; - using WixToolset.Data.Rows; using WixToolset.Extensibility; - using WixToolset.Msi; - using WixToolset.Core.Native; - using WixToolset.Ole32; + using System.Collections.Generic; /// /// Unbinder core of the WiX toolset. /// - public sealed class Unbinder : IMessageHandler + public sealed class Unbinder { - private string emptyFile; - private bool isAdminImage; - private int sectionCount; - private bool suppressDemodularization; - private bool suppressExtractCabinets; private TableDefinitionCollection tableDefinitions; private ArrayList unbinderExtensions; // private TempFileCollection tempFiles; @@ -45,61 +26,32 @@ namespace WixToolset this.unbinderExtensions = new ArrayList(); } + public IEnumerable BackendFactories { get; } + /// /// Gets or sets whether the input msi is an admin image. /// /// Set to true if the input msi is part of an admin image. - public bool IsAdminImage - { - get { return this.isAdminImage; } - set { this.isAdminImage = value; } - } + public bool IsAdminImage { get; set; } /// /// Gets or sets the option to suppress demodularizing values. /// /// The option to suppress demodularizing values. - public bool SuppressDemodularization - { - get { return this.suppressDemodularization; } - set { this.suppressDemodularization = value; } - } + public bool SuppressDemodularization { get; set; } /// /// Gets or sets the option to suppress extracting cabinets. /// /// The option to suppress extracting cabinets. - public bool SuppressExtractCabinets - { - get { return this.suppressExtractCabinets; } - set { this.suppressExtractCabinets = value; } - } + public bool SuppressExtractCabinets { get; set; } /// /// Gets or sets the temporary path for the Binder. If left null, the binder /// will use %TEMP% environment variable. /// /// Path to temp files. - public string TempFilesLocation - { - get - { - // return null == this.tempFiles ? String.Empty : this.tempFiles.BasePath; - return Path.GetTempPath(); - } - - // set - // { - // if (null == value) - // { - // this.tempFiles = new TempFileCollection(); - // } - // else - // { - // this.tempFiles = new TempFileCollection(value); - // } - // } - } + public string TempFilesLocation => Path.GetTempPath(); /// /// Adds extension data. @@ -156,1336 +108,25 @@ namespace WixToolset // if we don't have the temporary files object yet, get one Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there - if (OutputType.Patch == outputType) - { - return this.UnbindPatch(file, exportBasePath); - } - else if (OutputType.Transform == outputType) - { - return this.UnbindTransform(file, exportBasePath); - } - else if (OutputType.Bundle == outputType) - { - return this.UnbindBundle(file, exportBasePath); - } - else // other database types - { - return this.UnbindDatabase(file, outputType, exportBasePath); - } - } - - /// - /// Cleans up the temp files used by the Decompiler. - /// - /// True if all files were deleted, false otherwise. - /// - /// This should be called after every call to Decompile to ensure there - /// are no conflicts between each decompiled database. - /// - public bool DeleteTempFiles() - { -#if REDO_IN_NETCORE - bool deleted = Common.DeleteTempFiles(this.tempFiles.BasePath, this); - - if (deleted) - { - this.tempFiles = null; // temp files have been deleted, no need to remember this now - } - - return deleted; -#endif - return true; - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } - - /// - /// Unbind an MSI database file. - /// - /// The database file. - /// The output type. - /// The path where files should be exported. - /// The unbound database. - private Output UnbindDatabase(string databaseFile, OutputType outputType, string exportBasePath) - { - Output output; - - try - { - using (Database database = new Database(databaseFile, OpenDatabase.ReadOnly)) - { - output = this.UnbindDatabase(databaseFile, database, outputType, exportBasePath, false); - - // extract the files from the cabinets - if (null != exportBasePath && !this.suppressExtractCabinets) - { - this.ExtractCabinets(output, database, databaseFile, exportBasePath); - } - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(WixErrors.OpenDatabaseFailed(databaseFile)); - } - - throw; - } - - return output; - } - - /// - /// Unbind an MSI database file. - /// - /// The database file. - /// The opened database. - /// The type of output to create. - /// The path where files should be exported. - /// Option to skip unbinding the _SummaryInformation table. - /// The output representing the database. - private Output UnbindDatabase(string databaseFile, Database database, OutputType outputType, string exportBasePath, bool skipSummaryInfo) - { - string modularizationGuid = null; - Output output = new Output(new SourceLineNumber(databaseFile)); - View validationView = null; - - // set the output type - output.Type = outputType; - - // get the codepage - database.Export("_ForceCodepage", this.TempFilesLocation, "_ForceCodepage.idt"); - using (StreamReader sr = File.OpenText(Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"))) - { - string line; - - while (null != (line = sr.ReadLine())) - { - string[] data = line.Split('\t'); - - if (2 == data.Length) - { - output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); - } - } - } - - // get the summary information table if it exists; it won't if unbinding a transform - if (!skipSummaryInfo) - { - using (SummaryInformation summaryInformation = new SummaryInformation(database)) - { - Table table = new Table(null, this.tableDefinitions["_SummaryInformation"]); - - for (int i = 1; 19 >= i; i++) - { - string value = summaryInformation.GetProperty(i); - - if (0 < value.Length) - { - Row row = table.CreateRow(output.SourceLineNumbers); - row[0] = i; - row[1] = value; - } - } - - output.Tables.Add(table); - } - } - - try - { - // open a view on the validation table if it exists - if (database.TableExists("_Validation")) - { - validationView = database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); - } - - // get the normal tables - using (View tablesView = database.OpenExecuteView("SELECT * FROM _Tables")) - { - while (true) - { - using (Record tableRecord = tablesView.Fetch()) - { - if (null == tableRecord) - { - break; - } - - string tableName = tableRecord.GetString(1); - - using (View tableView = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) - { - List columns; - using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES), - columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES)) - { - // index the primary keys - HashSet tablePrimaryKeys = new HashSet(); - using (Record primaryKeysRecord = database.PrimaryKeys(tableName)) - { - int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); - - for (int i = 1; i <= primaryKeysFieldCount; i++) - { - tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); - } - } - - int columnCount = columnNameRecord.GetFieldCount(); - columns = new List(columnCount); - for (int i = 1; i <= columnCount; i++) - { - string columnName = columnNameRecord.GetString(i); - string idtType = columnTypeRecord.GetString(i); - - ColumnType columnType; - int length; - bool nullable; - - ColumnCategory columnCategory = ColumnCategory.Unknown; - ColumnModularizeType columnModularizeType = ColumnModularizeType.None; - bool primary = tablePrimaryKeys.Contains(columnName); - bool minValueSet = false; - int minValue = -1; - bool maxValueSet = false; - int maxValue = -1; - string keyTable = null; - bool keyColumnSet = false; - int keyColumn = -1; - string category = null; - string set = null; - string description = null; - - // get the column type, length, and whether its nullable - switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) - { - case 'i': - columnType = ColumnType.Number; - break; - case 'l': - columnType = ColumnType.Localized; - break; - case 's': - columnType = ColumnType.String; - break; - case 'v': - columnType = ColumnType.Object; - break; - default: - // TODO: error - columnType = ColumnType.Unknown; - break; - } - length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); - nullable = Char.IsUpper(idtType[0]); - - // try to get validation information - if (null != validationView) - { - using (Record validationRecord = new Record(2)) - { - validationRecord.SetString(1, tableName); - validationRecord.SetString(2, columnName); - - validationView.Execute(validationRecord); - } - - using (Record validationRecord = validationView.Fetch()) - { - if (null != validationRecord) - { - string validationNullable = validationRecord.GetString(3); - minValueSet = !validationRecord.IsNull(4); - minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); - maxValueSet = !validationRecord.IsNull(5); - maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); - keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); - keyColumnSet = !validationRecord.IsNull(7); - keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); - category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); - set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); - description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); - - // check the validation nullable value against the column definition - if (null == validationNullable) - { - // TODO: warn for illegal validation nullable column - } - else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) - { - // TODO: warn for mismatch between column definition and validation nullable - } - - // convert category to ColumnCategory - if (null != category) - { - try - { - columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); - } - catch (ArgumentException) - { - columnCategory = ColumnCategory.Unknown; - } - } - } - else - { - // TODO: warn about no validation information - } - } - } - - // guess the modularization type - if ("Icon" == keyTable && 1 == keyColumn) - { - columnModularizeType = ColumnModularizeType.Icon; - } - else if ("Condition" == columnName) - { - columnModularizeType = ColumnModularizeType.Condition; - } - else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) - { - columnModularizeType = ColumnModularizeType.Property; - } - else if (ColumnCategory.Identifier == columnCategory) - { - columnModularizeType = ColumnModularizeType.Column; - } - - columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); - } - } - - TableDefinition tableDefinition = new TableDefinition(tableName, columns, false, false); - - // use our table definitions if core properties are the same; this allows us to take advantage - // of wix concepts like localizable columns which current code assumes - if (this.tableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.tableDefinitions[tableName])) - { - tableDefinition = this.tableDefinitions[tableName]; - } - - Table table = new Table(null, tableDefinition); - - while (true) - { - using (Record rowRecord = tableView.Fetch()) - { - if (null == rowRecord) - { - break; - } - - int recordCount = rowRecord.GetFieldCount(); - Row row = table.CreateRow(output.SourceLineNumbers); - - for (int i = 0; recordCount > i && row.Fields.Length > i; i++) - { - if (rowRecord.IsNull(i + 1)) - { - if (!row.Fields[i].Column.Nullable) - { - // TODO: display an error for a null value in a non-nullable field OR - // display a warning and put an empty string in the value to let the compiler handle it - // (the second option is risky because the later code may make certain assumptions about - // the contents of a row value) - } - } - else - { - switch (row.Fields[i].Column.Type) - { - case ColumnType.Number: - bool success = false; - int 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); - } - - if (!success) - { - this.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); - } - break; - case ColumnType.Object: - string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; - - if (null != exportBasePath) - { - string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); - sourceFile = Path.Combine(exportBasePath, relativeSourceFile); - - // ensure the parent directory exists - System.IO.Directory.CreateDirectory(Path.Combine(exportBasePath, tableName)); - - using (FileStream fs = System.IO.File.Create(sourceFile)) - { - int bytesRead; - byte[] buffer = new byte[512]; - - while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - } - - row[i] = sourceFile; - break; - default: - string value = rowRecord.GetString(i + 1); + var context = new UnbindContext(); + context.InputFilePath = file; + context.ExportBasePath = exportBasePath; + context.IntermediateFolder = this.TempFilesLocation; + context.IsAdminImage = this.IsAdminImage; + context.SuppressDemodularization = this.SuppressDemodularization; + context.SuppressExtractCabinets = this.SuppressExtractCabinets; - switch (row.Fields[i].Column.Category) - { - case ColumnCategory.Guid: - value = value.ToUpper(CultureInfo.InvariantCulture); - break; - } - - // de-modularize - if (!this.suppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) - { - 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}"); - - if (null == modularizationGuid) - { - Match match = modularization.Match(value); - if (match.Success) - { - modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); - } - } - - value = modularization.Replace(value, String.Empty); - } - - // escape "$(" for the preprocessor - value = value.Replace("$(", "$$("); - - // escape things that look like wix variables - MatchCollection matches = Common.WixVariableRegex.Matches(value); - for (int j = matches.Count - 1; 0 <= j; j--) - { - value = value.Insert(matches[j].Index, "!"); - } - - row[i] = value; - break; - } - } - } - } - } - - output.Tables.Add(table); - } - - } - } - } - } - finally - { - if (null != validationView) - { - validationView.Close(); - } - } - - // set the modularization guid as the PackageCode - if (null != modularizationGuid) + foreach (var factory in this.BackendFactories) { - Table table = output.Tables["_SummaryInformation"]; - - foreach (Row row in table.Rows) + if (factory.TryCreateBackend(outputType.ToString(), file, null, out var backend)) { - if (9 == (int)row[0]) // PID_REVNUMBER - { - row[1] = modularizationGuid; - } + return backend.Unbind(context); } } - if (this.isAdminImage) - { - GenerateWixFileTable(databaseFile, output); - GenerateSectionIds(output); - } - - return output; - } - - /// - /// Creates section ids on rows which form logical groupings of resources. - /// - /// The Output that represents the msi database. - private void GenerateSectionIds(Output output) - { - // First assign and index section ids for the tables that are in their own sections. - AssignSectionIdsToTable(output.Tables["Binary"], 0); - Hashtable componentSectionIdIndex = AssignSectionIdsToTable(output.Tables["Component"], 0); - Hashtable customActionSectionIdIndex = AssignSectionIdsToTable(output.Tables["CustomAction"], 0); - AssignSectionIdsToTable(output.Tables["Directory"], 0); - Hashtable featureSectionIdIndex = AssignSectionIdsToTable(output.Tables["Feature"], 0); - AssignSectionIdsToTable(output.Tables["Icon"], 0); - Hashtable digitalCertificateSectionIdIndex = AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); - AssignSectionIdsToTable(output.Tables["Property"], 0); - - // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here. - Hashtable fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); - Hashtable appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); - Hashtable odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); - Hashtable odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); - Hashtable registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); - Hashtable serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0); - - // Now handle all the tables which only rely on previous indexes and order does not matter. - foreach (Table table in output.Tables) - { - switch (table.Name) - { - case "WixFile": - case "MsiFileHash": - ConnectTableToSection(table, fileSectionIdIndex, 0); - break; - case "MsiAssembly": - case "MsiAssemblyName": - ConnectTableToSection(table, componentSectionIdIndex, 0); - break; - case "MsiPackageCertificate": - case "MsiPatchCertificate": - ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); - break; - case "CreateFolder": - case "FeatureComponents": - case "MoveFile": - case "ReserveCost": - case "ODBCTranslator": - ConnectTableToSection(table, componentSectionIdIndex, 1); - break; - case "TypeLib": - ConnectTableToSection(table, componentSectionIdIndex, 2); - break; - case "Shortcut": - case "Environment": - ConnectTableToSection(table, componentSectionIdIndex, 3); - break; - case "RemoveRegistry": - ConnectTableToSection(table, componentSectionIdIndex, 4); - break; - case "ServiceControl": - ConnectTableToSection(table, componentSectionIdIndex, 5); - break; - case "IniFile": - case "RemoveIniFile": - ConnectTableToSection(table, componentSectionIdIndex, 7); - break; - case "AppId": - ConnectTableToSection(table, appIdSectionIdIndex, 0); - break; - case "Condition": - ConnectTableToSection(table, featureSectionIdIndex, 0); - break; - case "ODBCSourceAttribute": - ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); - break; - case "ODBCAttribute": - ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); - break; - case "AdminExecuteSequence": - case "AdminUISequence": - case "AdvtExecuteSequence": - case "AdvtUISequence": - case "InstallExecuteSequence": - case "InstallUISequence": - ConnectTableToSection(table, customActionSectionIdIndex, 0); - break; - case "LockPermissions": - case "MsiLockPermissions": - foreach (Row row in table.Rows) - { - string lockObject = (string)row[0]; - string tableName = (string)row[1]; - switch (tableName) - { - case "File": - row.SectionId = (string)fileSectionIdIndex[lockObject]; - break; - case "Registry": - row.SectionId = (string)registrySectionIdIndex[lockObject]; - break; - case "ServiceInstall": - row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; - break; - } - } - break; - } - } - - // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. - foreach (IUnbinderExtension extension in this.unbinderExtensions) - { - extension.GenerateSectionIds(output); - } - } - - /// - /// Creates new section ids on all the rows in a table. - /// - /// 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) - { - Hashtable hashtable = new Hashtable(); - if (null != table) - { - foreach (Row row in table.Rows) - { - row.SectionId = GetNewSectionId(); - hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); - } - } - return hashtable; - } - - /// - /// Connects a table's rows to an already sectioned table. - /// - /// 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) - { - if (null != table) - { - foreach (Row row in table.Rows) - { - if (sectionIdIndex.ContainsKey(row[rowIndex])) - { - row.SectionId = (string)sectionIdIndex[row[rowIndex]]; - } - } - } - } - - /// - /// 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. - /// 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) - { - Hashtable newHashTable = new Hashtable(); - if (null != table) - { - foreach (Row row in table.Rows) - { - if (!sectionIdIndex.ContainsKey(row[rowIndex])) - { - continue; - } - - row.SectionId = (string)sectionIdIndex[row[rowIndex]]; - if (null != row[rowPrimaryKeyIndex]) - { - newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); - } - } - } - return newHashTable; - } - - /// - /// 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); - } - - /// - /// 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, Output output) - { - string adminRootPath = Path.GetDirectoryName(databaseFile); - - Hashtable componentDirectoryIndex = new Hashtable(); - Table componentTable = output.Tables["Component"]; - foreach (Row row in componentTable.Rows) - { - componentDirectoryIndex.Add(row[0], row[2]); - } - - // Index full source paths for all directories - Hashtable directoryDirectoryParentIndex = new Hashtable(); - Hashtable directoryFullPathIndex = new Hashtable(); - Hashtable directorySourceNameIndex = new Hashtable(); - Table directoryTable = output.Tables["Directory"]; - foreach (Row 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])); - } - } - - foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) - { - if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) - { - GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); - } - } - - Table fileTable = output.Tables["File"]; - Table wixFileTable = output.EnsureTable(this.tableDefinitions["WixFile"]); - foreach (Row row in fileTable.Rows) - { - WixFileRow 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])); - - if (!File.Exists(wixFileRow.Source)) - { - throw new WixException(WixErrors.WixFileNotFound(wixFileRow.Source)); - } - - wixFileTable.Rows.Add(wixFileRow); - } - } - - /// - /// Gets the full path of a directory. Populates the full path index with the directory's full path and all of its parent directorie's full paths. - /// - /// The directory identifier. - /// The Hashtable containing all the directory to directory parent mapping. - /// The Hashtable containing all the directory to source name mapping. - /// The Hashtable containing a mapping between all of the directories and their previously calculated full paths. - /// The full path to the directory. - private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) - { - string parent = (string)directoryDirectoryParentIndex[directory]; - string sourceName = (string)directorySourceNameIndex[directory]; - - string parentFullPath; - if (directoryFullPathIndex.ContainsKey(parent)) - { - parentFullPath = (string)directoryFullPathIndex[parent]; - } - else - { - parentFullPath = GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); - } - - if (null == sourceName) - { - sourceName = String.Empty; - } - - string fullPath = Path.Combine(parentFullPath, sourceName); - directoryFullPathIndex.Add(directory, fullPath); - - return fullPath; - } - - /// - /// Get the source name in an admin image. - /// - /// The Filename value. - /// The source name of the directory in an admin image. - private static string GetAdminSourceName(string value) - { - string name = null; - string[] names; - string shortname = null; - string shortsourcename = null; - string sourcename = null; - - names = Installer.GetNames(value); - - if (null != names[0] && "." != names[0]) - { - if (null != names[1]) - { - shortname = names[0]; - } - else - { - name = names[0]; - } - } - - if (null != names[1]) - { - name = names[1]; - } - - if (null != names[2]) - { - if (null != names[3]) - { - shortsourcename = names[2]; - } - else - { - sourcename = names[2]; - } - } - - if (null != names[3]) - { - sourcename = names[3]; - } - - if (null != sourcename) - { - return sourcename; - } - else if (null != shortsourcename) - { - return shortsourcename; - } - else if (null != name) - { - return name; - } - else - { - return shortname; - } - } - - /// - /// Unbind an MSP patch file. - /// - /// The patch file. - /// The path where files should be exported. - /// The unbound patch. - private Output UnbindPatch(string patchFile, string exportBasePath) - { - 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(patchFile, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - patch = this.UnbindDatabase(patchFile, database, OutputType.Patch, exportBasePath, false); - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(WixErrors.OpenDatabaseFailed(patchFile)); - } - - throw; - } - - // retrieve the transforms (they are in substorages) - using (Storage storage = Storage.Open(patchFile, 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(this.TempFilesLocation, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); - - // ensure the parent directory exists - System.IO.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 - Output transform = this.UnbindTransform(transformFile, (null == exportBasePath ? null : Path.Combine(exportBasePath, subStorageName))); - 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 != exportBasePath && !this.suppressExtractCabinets) - { - using (Database database = new Database(patchFile, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - foreach (SubStorage subStorage in patch.SubStorages) - { - // only patch transforms should carry files - if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) - { - this.ExtractCabinets(subStorage.Data, database, patchFile, exportBasePath); - } - } - } - } - - return patch; - } - - /// - /// Unbind an MSI transform file. - /// - /// The transform file. - /// The path where files should be exported. - /// The unbound transform. - private Output UnbindTransform(string transformFile, string exportBasePath) - { - Output transform = new Output(new SourceLineNumber(transformFile)); - transform.Type = OutputType.Transform; - - // get the summary information table - using (SummaryInformation summaryInformation = new SummaryInformation(transformFile)) - { - Table table = transform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); - - for (int i = 1; 19 >= i; i++) - { - string value = summaryInformation.GetProperty(i); - - if (0 < value.Length) - { - Row row = table.CreateRow(transform.SourceLineNumbers); - row[0] = i; - row[1] = value; - } - } - } - - // create a schema msi which hopefully matches the table schemas in the transform - Output schemaOutput = new Output(null); - string msiDatabaseFile = Path.Combine(this.TempFilesLocation, "schema.msi"); - foreach (TableDefinition tableDefinition in this.tableDefinitions) - { - // skip unreal tables and the Patch table - if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) - { - schemaOutput.EnsureTable(tableDefinition); - } - } - - Hashtable addedRows = new Hashtable(); - Table transformViewTable; - - // Bind the schema msi. - this.GenerateDatabase(schemaOutput, msiDatabaseFile); - - // apply the transform to the database and retrieve the modifications - using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) - { - // apply the transform with the ViewTransform option to collect all the modifications - msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); - - // unbind the database - Output transformViewOutput = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); - - // index the added and possibly modified rows (added rows may also appears as modified rows) - transformViewTable = transformViewOutput.Tables["_TransformView"]; - Hashtable modifiedRows = new Hashtable(); - foreach (Row row in transformViewTable.Rows) - { - string tableName = (string)row[0]; - string columnName = (string)row[1]; - string primaryKeys = (string)row[2]; - - if ("INSERT" == columnName) - { - string index = String.Concat(tableName, ':', primaryKeys); - - addedRows.Add(index, null); - } - else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row - { - string index = String.Concat(tableName, ':', primaryKeys); - - modifiedRows[index] = row; - } - } - - // create placeholder rows for modified rows to make the transform insert the updated values when its applied - foreach (Row row in modifiedRows.Values) - { - string tableName = (string)row[0]; - string columnName = (string)row[1]; - string primaryKeys = (string)row[2]; - - string index = String.Concat(tableName, ':', primaryKeys); - - // ignore information for added rows - if (!addedRows.Contains(index)) - { - Table table = schemaOutput.Tables[tableName]; - this.CreateRow(table, primaryKeys, true); - } - } - } - - // Re-bind the schema output with the placeholder rows. - this.GenerateDatabase(schemaOutput, msiDatabaseFile); - - // apply the transform to the database and retrieve the modifications - using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) - { - try - { - // apply the transform - msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All); - - // commit the database to guard against weird errors with streams - msiDatabase.Commit(); - } - catch (Win32Exception ex) - { - if (0x65B == ex.NativeErrorCode) - { - // this commonly happens when the transform was built - // against a database schema different from the internal - // table definitions - throw new WixException(WixErrors.TransformSchemaMismatch()); - } - } - - // unbind the database - Output output = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); - - // index all the rows to easily find modified rows - Hashtable rows = new Hashtable(); - foreach (Table table in output.Tables) - { - foreach (Row row in table.Rows) - { - rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); - } - } - - // process the _TransformView rows into transform rows - foreach (Row row in transformViewTable.Rows) - { - string tableName = (string)row[0]; - string columnName = (string)row[1]; - string primaryKeys = (string)row[2]; - - Table table = transform.EnsureTable(this.tableDefinitions[tableName]); - - if ("CREATE" == columnName) // added table - { - table.Operation = TableOperation.Add; - } - else if ("DELETE" == columnName) // deleted row - { - Row deletedRow = this.CreateRow(table, primaryKeys, false); - deletedRow.Operation = RowOperation.Delete; - } - else if ("DROP" == columnName) // dropped table - { - table.Operation = TableOperation.Drop; - } - else if ("INSERT" == columnName) // added row - { - string index = String.Concat(tableName, ':', primaryKeys); - Row addedRow = (Row)rows[index]; - addedRow.Operation = RowOperation.Add; - table.Rows.Add(addedRow); - } - else if (null != primaryKeys) // modified row - { - string index = String.Concat(tableName, ':', primaryKeys); - - // the _TransformView table includes information for added rows - // that looks like modified rows so it sometimes needs to be ignored - if (!addedRows.Contains(index)) - { - Row modifiedRow = (Row)rows[index]; - - // mark the field as modified - int indexOfModifiedValue = -1; - for (int i = 0; i < modifiedRow.TableDefinition.Columns.Count; ++i) - { - if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal)) - { - indexOfModifiedValue = i; - break; - } - } - modifiedRow.Fields[indexOfModifiedValue].Modified = true; - - // move the modified row into the transform the first time its encountered - if (RowOperation.None == modifiedRow.Operation) - { - modifiedRow.Operation = RowOperation.Modify; - table.Rows.Add(modifiedRow); - } - } - } - else // added column - { - ColumnDefinition column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); - column.Added = true; - } - } - } - - return transform; - } - - private void GenerateDatabase(Output output, string databaseFile) - { - GenerateDatabaseCommand command = new GenerateDatabaseCommand(); - command.Extensions = Enumerable.Empty(); - command.FileManagers = Enumerable.Empty(); - command.Output = output; - command.OutputPath = databaseFile; - command.KeepAddedColumns = true; - command.UseSubDirectory = false; - command.SuppressAddingValidationRows = true; - command.TableDefinitions = this.tableDefinitions; - command.TempFilesLocation = this.TempFilesLocation; - command.Codepage = -1; - command.Execute(); - } - - /// - /// Unbind a bundle. - /// - /// The bundle file. - /// The path where files should be exported. - /// The unbound bundle. - private Output UnbindBundle(string bundleFile, string exportBasePath) - { - string uxExtractPath = Path.Combine(exportBasePath, "UX"); - string acExtractPath = Path.Combine(exportBasePath, "AttachedContainer"); - - using (BurnReader reader = BurnReader.Open(bundleFile)) - { - reader.ExtractUXContainer(uxExtractPath, this.TempFilesLocation); - reader.ExtractAttachedContainer(acExtractPath, this.TempFilesLocation); - } + // TODO: Display message that could not find a unbinder for output type? return null; } - - /// - /// Create a deleted or modified row. - /// - /// The table containing the row. - /// The primary keys of the row. - /// Option to set all required fields with placeholder values. - /// The new row. - private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) - { - Row row = table.CreateRow(null); - - string[] primaryKeyParts = primaryKeys.Split('\t'); - int primaryKeyPartIndex = 0; - - for (int i = 0; i < table.Definition.Columns.Count; i++) - { - ColumnDefinition columnDefinition = table.Definition.Columns[i]; - - if (columnDefinition.PrimaryKey) - { - if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) - { - row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); - } - else - { - row[i] = primaryKeyParts[primaryKeyPartIndex++]; - } - } - else if (setRequiredFields) - { - if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) - { - row[i] = 1; - } - else if (ColumnType.Object == columnDefinition.Type) - { - if (null == this.emptyFile) - { - this.emptyFile = Path.GetTempFileName() + ".empty"; - using (FileStream fileStream = File.Create(this.emptyFile)) - { - } - } - - row[i] = this.emptyFile; - } - else - { - row[i] = "1"; - } - } - } - - return row; - } - - /// - /// Extract the cabinets from a database. - /// - /// The output to use when finding cabinets. - /// The database containing the cabinets. - /// The location of the database file. - /// The path where the files should be exported. - private void ExtractCabinets(Output output, Database database, string databaseFile, string exportBasePath) - { - string databaseBasePath = Path.GetDirectoryName(databaseFile); - StringCollection cabinetFiles = new StringCollection(); - SortedList embeddedCabinets = new SortedList(); - - // index all of the cabinet files - if (OutputType.Module == output.Type) - { - embeddedCabinets.Add(0, "MergeModule.CABinet"); - } - else if (null != output.Tables["Media"]) - { - foreach (MediaRow mediaRow in output.Tables["Media"].Rows) - { - if (null != mediaRow.Cabinet) - { - if (OutputType.Product == output.Type || - (OutputType.Transform == output.Type && RowOperation.Add == mediaRow.Operation)) - { - if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) - { - embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); - } - else - { - cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet)); - } - } - } - } - } - - // extract the embedded cabinet files from the database - if (0 < embeddedCabinets.Count) - { - using (View streamsView = database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) - { - foreach (int diskId in embeddedCabinets.Keys) - { - using (Record record = new Record(1)) - { - record.SetString(1, (string)embeddedCabinets[diskId]); - streamsView.Execute(record); - } - - using (Record record = streamsView.Fetch()) - { - if (null != record) - { - // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not case-sensitive, - // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work - string cabinetFile = Path.Combine(this.TempFilesLocation, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); - - // ensure the parent directory exists - System.IO.Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); - - using (FileStream fs = System.IO.File.Create(cabinetFile)) - { - int bytesRead; - byte[] buffer = new byte[512]; - - while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - - cabinetFiles.Add(cabinetFile); - } - else - { - // TODO: warning about missing embedded cabinet - } - } - } - } - } - - // extract the cabinet files - if (0 < cabinetFiles.Count) - { - string fileDirectory = Path.Combine(exportBasePath, "File"); - - // delete the directory and its files to prevent cab extraction due to an existing file - if (Directory.Exists(fileDirectory)) - { - Directory.Delete(fileDirectory, true); - } - - // ensure the directory exists or extraction will fail - Directory.CreateDirectory(fileDirectory); - - foreach (string cabinetFile in cabinetFiles) - { - using (WixExtractCab extractCab = new WixExtractCab()) - { - try - { - extractCab.Extract(cabinetFile, fileDirectory); - } - catch (FileNotFoundException) - { - throw new WixException(WixErrors.FileNotFound(new SourceLineNumber(databaseFile), cabinetFile)); - } - } - } - } - } } } diff --git a/src/WixToolset.Core/Uuid.cs b/src/WixToolset.Core/Uuid.cs index 2e599793..d512d92f 100644 --- a/src/WixToolset.Core/Uuid.cs +++ b/src/WixToolset.Core/Uuid.cs @@ -10,7 +10,7 @@ namespace WixToolset /// /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. /// - internal sealed class Uuid + public sealed class Uuid { /// /// Protect the constructor. diff --git a/src/WixToolset.Core/Validator.cs b/src/WixToolset.Core/Validator.cs deleted file mode 100644 index 6420b9b7..00000000 --- a/src/WixToolset.Core/Validator.cs +++ /dev/null @@ -1,401 +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 -{ - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Msi; - using WixToolset.Core.Native; - - /// - /// Runs internal consistency evaluators (ICEs) from cub files against a database. - /// - public sealed class Validator : IMessageHandler - { - private string actionName; - private StringCollection cubeFiles; - private ValidatorExtension extension; - private string[] ices; - private Output output; - private string[] suppressedICEs; - private InstallUIHandler validationUIHandler; - private bool validationSessionComplete; - - /// - /// Instantiate a new Validator. - /// - public Validator() - { - this.cubeFiles = new StringCollection(); - this.extension = new ValidatorExtension(); - this.validationUIHandler = new InstallUIHandler(this.ValidationUIHandler); - } - - /// - /// Gets or sets a that directs messages from the validator. - /// - /// A that directs messages from the validator. - public ValidatorExtension Extension - { - get { return this.extension; } - set { this.extension = value; } - } - - /// - /// Gets or sets the list of ICEs to run. - /// - /// The list of ICEs. - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] ICEs - { - get { return this.ices; } - set { this.ices = value; } - } - - /// - /// Gets or sets the output used for finding source line information. - /// - /// The output used for finding source line information. - public Output Output - { - // cache Output object until validation for changes in extension - get { return this.output; } - set { this.output = value; } - } - - /// - /// Gets or sets the suppressed ICEs. - /// - /// The suppressed ICEs. - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] SuppressedICEs - { - get { return this.suppressedICEs; } - set { this.suppressedICEs = value; } - } - - /// - /// Sets the temporary path for the Binder. - /// - public string TempFilesLocation { private get; set; } - - /// - /// Add a cube file to the validation run. - /// - /// A cube file. - public void AddCubeFile(string cubeFile) - { - this.cubeFiles.Add(cubeFile); - } - - /// - /// Validate a database. - /// - /// The database to validate. - /// true if validation succeeded; false otherwise. - public void Validate(string databaseFile) - { - Dictionary indexedICEs = new Dictionary(); - Dictionary indexedSuppressedICEs = new Dictionary(); - int previousUILevel = (int)InstallUILevels.Basic; - IntPtr previousHwnd = IntPtr.Zero; - InstallUIHandler previousUIHandler = null; - - if (null == databaseFile) - { - throw new ArgumentNullException("databaseFile"); - } - - // initialize the validator extension - this.extension.DatabaseFile = databaseFile; - this.extension.Output = this.output; - this.extension.InitializeValidator(); - - // Ensure the temporary files can be created. - Directory.CreateDirectory(this.TempFilesLocation); - - // index the ICEs - if (null != this.ices) - { - foreach (string ice in this.ices) - { - indexedICEs[ice] = null; - } - } - - // index the suppressed ICEs - if (null != this.suppressedICEs) - { - foreach (string suppressedICE in this.suppressedICEs) - { - indexedSuppressedICEs[suppressedICE] = null; - } - } - - // copy the database to a temporary location so it can be manipulated - string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(databaseFile)); - File.Copy(databaseFile, tempDatabaseFile); - - // remove the read-only property from the temporary database - FileAttributes attributes = File.GetAttributes(tempDatabaseFile); - File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly); - - Mutex mutex = new Mutex(false, "WixValidator"); - try - { - if (!mutex.WaitOne(0, false)) - { - this.OnMessage(WixVerboses.ValidationSerialized()); - mutex.WaitOne(); - } - - using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct)) - { - bool propertyTableExists = database.TableExists("Property"); - string productCode = null; - - // remove the product code from the database before opening a session to prevent opening an installed product - if (propertyTableExists) - { - using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'")) - { - using (Record record = view.Fetch()) - { - if (null != record) - { - productCode = record.GetString(1); - - using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) - { - } - } - } - } - } - - // merge in the cube databases - foreach (string cubeFile in this.cubeFiles) - { - try - { - using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly)) - { - try - { - database.Merge(cubeDatabase, "MergeConflicts"); - } - catch - { - // ignore merge errors since they are expected in the _Validation table - } - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(WixErrors.CubeFileNotFound(cubeFile)); - } - - throw; - } - } - - // commit the database before proceeding to ensure the streams don't get confused - database.Commit(); - - // the property table may have been added to the database - // from a cub database without the proper validation rows - if (!propertyTableExists) - { - using (View view = database.OpenExecuteView("DROP table `Property`")) - { - } - } - - // get all the action names for ICEs which have not been suppressed - List actions = new List(); - using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) - { - while (true) - { - using (Record record = view.Fetch()) - { - if (null == record) - { - break; - } - - string action = record.GetString(1); - - if (!indexedSuppressedICEs.ContainsKey(action)) - { - actions.Add(action); - } - } - } - } - - if (0 != indexedICEs.Count) - { - // Walk backwards and remove those that arent in the list - for (int i = actions.Count - 1; 0 <= i; i--) - { - if (!indexedICEs.ContainsKey(actions[i])) - { - actions.RemoveAt(i); - } - } - } - - // disable the internal UI handler and set an external UI handler - previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd); - previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero); - - // create a session for running the ICEs - this.validationSessionComplete = false; - using (Session session = new Session(database)) - { - // add the product code back into the database - if (null != productCode) - { - // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up - using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) - { - } - - using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode))) - { - } - } - - foreach (string action in actions) - { - this.actionName = action; - try - { - session.DoAction(action); - } - catch (Win32Exception e) - { - if (!Messaging.Instance.EncounteredError) - { - throw e; - } - // TODO: Review why this was clearing the error state when an exception had happened but an error was already encountered. That's weird. - //else - //{ - // this.encounteredError = false; - //} - } - this.actionName = null; - } - - // Mark the validation session complete so we ignore any messages that MSI may fire - // during session clean-up. - this.validationSessionComplete = true; - } - } - } - catch (Win32Exception e) - { - // avoid displaying errors twice since one may have already occurred in the UI handler - if (!Messaging.Instance.EncounteredError) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - // databaseFile is not passed since during light - // this would be the temporary copy and there would be - // no final output since the error occured; during smoke - // they should know the path passed into smoke - this.OnMessage(WixErrors.ValidationFailedToOpenDatabase()); - } - else if (0x64D == e.NativeErrorCode) - { - this.OnMessage(WixErrors.ValidationFailedDueToLowMsiEngine()); - } - else if (0x654 == e.NativeErrorCode) - { - this.OnMessage(WixErrors.ValidationFailedDueToInvalidPackage()); - } - else if (0x658 == e.NativeErrorCode) - { - this.OnMessage(WixErrors.ValidationFailedDueToMultilanguageMergeModule()); - } - else if (0x659 == e.NativeErrorCode) - { - this.OnMessage(WixWarnings.ValidationFailedDueToSystemPolicy()); - } - else - { - string msgTemp = e.Message; - - if (null != this.actionName) - { - msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message); - } - - this.OnMessage(WixErrors.Win32Exception(e.NativeErrorCode, msgTemp)); - } - } - } - finally - { - Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero); - Installer.SetInternalUI(previousUILevel, ref previousHwnd); - - this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag. - - mutex.ReleaseMutex(); - this.cubeFiles.Clear(); - this.extension.FinalizeValidator(); - } - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - this.extension.OnMessage(e); - } - - /// - /// The validation external UI handler. - /// - /// Pointer to an application context. - /// This parameter can be used for error checking. - /// Specifies a combination of one message box style, - /// one message box icon type, one default button, and one installation message type. - /// Specifies the message text. - /// -1 for an error, 0 if no action was taken, 1 if OK, 3 to abort. - private int ValidationUIHandler(IntPtr context, uint messageType, string message) - { - try - { - // If we're getting messges during the validation session, send them to - // the extension. Otherwise, ignore the messages. - if (!this.validationSessionComplete) - { - this.extension.Log(message, this.actionName); - } - } - catch (WixException ex) - { - this.OnMessage(ex.Error); - } - - return 1; - } - } -} diff --git a/src/WixToolset.Core/VerifyInterop.cs b/src/WixToolset.Core/VerifyInterop.cs deleted file mode 100644 index 81fbec65..00000000 --- a/src/WixToolset.Core/VerifyInterop.cs +++ /dev/null @@ -1,68 +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 -{ - using System; - using System.Collections; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - internal class VerifyInterop - { - internal const string GenericVerify2 = "00AAC56B-CD44-11d0-8CC2-00C04FC295EE"; - internal const uint WTD_UI_NONE = 2; - internal const uint WTD_REVOKE_NONE = 0; - internal const uint WTD_CHOICE_CATALOG = 2; - internal const uint WTD_STATEACTION_VERIFY = 1; - internal const uint WTD_REVOCATION_CHECK_NONE = 0x10; - internal const int ErrorInsufficientBuffer = 122; - - [StructLayout(LayoutKind.Sequential)] - internal struct WinTrustData - { - internal uint cbStruct; - internal IntPtr pPolicyCallbackData; - internal IntPtr pSIPClientData; - internal uint dwUIChoice; - internal uint fdwRevocationChecks; - internal uint dwUnionChoice; - internal IntPtr pCatalog; - internal uint dwStateAction; - internal IntPtr hWVTStateData; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pwszURLReference; - internal uint dwProvFlags; - internal uint dwUIContext; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct WinTrustCatalogInfo - { - internal uint cbStruct; - internal uint dwCatalogVersion; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pcwszCatalogFilePath; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pcwszMemberTag; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pcwszMemberFilePath; - internal IntPtr hMemberFile; - internal IntPtr pbCalculatedFileHash; - internal uint cbCalculatedFileHash; - internal IntPtr pcCatalogContext; - } - - [DllImport("wintrust.dll", SetLastError = true)] - internal static extern long WinVerifyTrust(IntPtr windowHandle, ref Guid actionGuid, ref WinTrustData trustData); - - [DllImport("wintrust.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptCATAdminCalcHashFromFileHandle( - IntPtr fileHandle, - [In, Out] - ref uint hashSize, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] - byte[] hashBytes, - uint flags); - } -} diff --git a/src/WixToolset.Core/WixComponentSearchInfo.cs b/src/WixToolset.Core/WixComponentSearchInfo.cs deleted file mode 100644 index dfd5d8ba..00000000 --- a/src/WixToolset.Core/WixComponentSearchInfo.cs +++ /dev/null @@ -1,64 +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 -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility class for all WixComponentSearches. - /// - internal class WixComponentSearchInfo : WixSearchInfo - { - public WixComponentSearchInfo(Row row) - : this((string)row[0], (string)row[1], (string)row[2], (int)row[3]) - { - } - - public WixComponentSearchInfo(string id, string guid, string productCode, int attributes) - : base(id) - { - this.Guid = guid; - this.ProductCode = productCode; - this.Attributes = (WixComponentSearchAttributes)attributes; - } - - public string Guid { get; private set; } - public string ProductCode { get; private set; } - public WixComponentSearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a component search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("MsiComponentSearch"); - this.WriteWixSearchAttributes(writer); - - writer.WriteAttributeString("ComponentId", this.Guid); - - if (!String.IsNullOrEmpty(this.ProductCode)) - { - writer.WriteAttributeString("ProductCode", this.ProductCode); - } - - if (0 != (this.Attributes & WixComponentSearchAttributes.KeyPath)) - { - writer.WriteAttributeString("Type", "keyPath"); - } - else if (0 != (this.Attributes & WixComponentSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (this.Attributes & WixComponentSearchAttributes.WantDirectory)) - { - writer.WriteAttributeString("Type", "directory"); - } - - writer.WriteEndElement(); - } - } - -} diff --git a/src/WixToolset.Core/WixFileSearchInfo.cs b/src/WixToolset.Core/WixFileSearchInfo.cs deleted file mode 100644 index e53f7bf7..00000000 --- a/src/WixToolset.Core/WixFileSearchInfo.cs +++ /dev/null @@ -1,54 +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 -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility class for all WixFileSearches (file and directory searches). - /// - internal class WixFileSearchInfo : WixSearchInfo - { - public WixFileSearchInfo(Row row) - : this((string)row[0], (string)row[1], (int)row[9]) - { - } - - public WixFileSearchInfo(string id, string path, int attributes) - : base(id) - { - this.Path = path; - this.Attributes = (WixFileSearchAttributes)attributes; - } - - public string Path { get; private set; } - public WixFileSearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a file/directory search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement((0 == (this.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); - this.WriteWixSearchAttributes(writer); - writer.WriteAttributeString("Path", this.Path); - if (WixFileSearchAttributes.WantExists == (this.Attributes & WixFileSearchAttributes.WantExists)) - { - writer.WriteAttributeString("Type", "exists"); - } - else if (WixFileSearchAttributes.WantVersion == (this.Attributes & WixFileSearchAttributes.WantVersion)) - { - // Can never get here for DirectorySearch. - writer.WriteAttributeString("Type", "version"); - } - else - { - writer.WriteAttributeString("Type", "path"); - } - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core/WixProductSearchInfo.cs b/src/WixToolset.Core/WixProductSearchInfo.cs deleted file mode 100644 index 4c57d8be..00000000 --- a/src/WixToolset.Core/WixProductSearchInfo.cs +++ /dev/null @@ -1,67 +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 -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility class for all WixProductSearches. - /// - internal class WixProductSearchInfo : WixSearchInfo - { - public WixProductSearchInfo(Row row) - : this((string)row[0], (string)row[1], (int)row[2]) - { - } - - public WixProductSearchInfo(string id, string guid, int attributes) - : base(id) - { - this.Guid = guid; - this.Attributes = (WixProductSearchAttributes)attributes; - } - - public string Guid { get; private set; } - public WixProductSearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a product search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("MsiProductSearch"); - this.WriteWixSearchAttributes(writer); - - if (0 != (this.Attributes & WixProductSearchAttributes.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", this.Guid); - } - else - { - writer.WriteAttributeString("ProductCode", this.Guid); - } - - if (0 != (this.Attributes & WixProductSearchAttributes.Version)) - { - writer.WriteAttributeString("Type", "version"); - } - else if (0 != (this.Attributes & WixProductSearchAttributes.Language)) - { - writer.WriteAttributeString("Type", "language"); - } - else if (0 != (this.Attributes & WixProductSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (this.Attributes & WixProductSearchAttributes.Assignment)) - { - writer.WriteAttributeString("Type", "assignment"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core/WixRegistrySearchInfo.cs b/src/WixToolset.Core/WixRegistrySearchInfo.cs deleted file mode 100644 index e8d7ce9b..00000000 --- a/src/WixToolset.Core/WixRegistrySearchInfo.cs +++ /dev/null @@ -1,92 +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 -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility class for all WixRegistrySearches. - /// - internal class WixRegistrySearchInfo : WixSearchInfo - { - public WixRegistrySearchInfo(Row row) - : this((string)row[0], (int)row[1], (string)row[2], (string)row[3], (int)row[4]) - { - } - - public WixRegistrySearchInfo(string id, int root, string key, string value, int attributes) - : base(id) - { - this.Root = root; - this.Key = key; - this.Value = value; - this.Attributes = (WixRegistrySearchAttributes)attributes; - } - - public int Root { get; private set; } - public string Key { get; private set; } - public string Value { get; private set; } - public WixRegistrySearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a registry search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("RegistrySearch"); - this.WriteWixSearchAttributes(writer); - - switch (this.Root) - { - case Core.Native.MsiInterop.MsidbRegistryRootClassesRoot: - writer.WriteAttributeString("Root", "HKCR"); - break; - case Core.Native.MsiInterop.MsidbRegistryRootCurrentUser: - writer.WriteAttributeString("Root", "HKCU"); - break; - case Core.Native.MsiInterop.MsidbRegistryRootLocalMachine: - writer.WriteAttributeString("Root", "HKLM"); - break; - case Core.Native.MsiInterop.MsidbRegistryRootUsers: - writer.WriteAttributeString("Root", "HKU"); - break; - } - - writer.WriteAttributeString("Key", this.Key); - - if (!String.IsNullOrEmpty(this.Value)) - { - writer.WriteAttributeString("Value", this.Value); - } - - bool existenceOnly = 0 != (this.Attributes & WixRegistrySearchAttributes.WantExists); - - writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); - - if (0 != (this.Attributes & WixRegistrySearchAttributes.Win64)) - { - writer.WriteAttributeString("Win64", "yes"); - } - - if (!existenceOnly) - { - if (0 != (this.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) - { - writer.WriteAttributeString("ExpandEnvironment", "yes"); - } - - // We *always* say this is VariableType="string". If we end up - // needing to be more specific, we will have to expand the "Format" - // attribute to allow "number" and "version". - - writer.WriteAttributeString("VariableType", "string"); - } - - writer.WriteEndElement(); - } - } - -} diff --git a/src/WixToolset.Core/WixSearchInfo.cs b/src/WixToolset.Core/WixSearchInfo.cs deleted file mode 100644 index 906365a2..00000000 --- a/src/WixToolset.Core/WixSearchInfo.cs +++ /dev/null @@ -1,53 +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 -{ - using System; - using System.Diagnostics; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility base class for all WixSearches. - /// - internal abstract class WixSearchInfo - { - public WixSearchInfo(string id) - { - this.Id = id; - } - - public void AddWixSearchRowInfo(Row row) - { - Debug.Assert((string)row[0] == Id); - Variable = (string)row[1]; - Condition = (string)row[2]; - } - - public string Id { get; private set; } - public string Variable { get; private set; } - public string Condition { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup a search. - /// - /// - public virtual void WriteXml(XmlTextWriter writer) - { - } - - /// - /// Writes attributes common to all WixSearch elements. - /// - /// - protected void WriteWixSearchAttributes(XmlTextWriter writer) - { - writer.WriteAttributeString("Id", this.Id); - writer.WriteAttributeString("Variable", this.Variable); - if (!String.IsNullOrEmpty(this.Condition)) - { - writer.WriteAttributeString("Condition", this.Condition); - } - } - } -} diff --git a/src/WixToolset.Core/WixStrings.Designer.cs b/src/WixToolset.Core/WixStrings.Designer.cs index 4ba9381a..75e2b908 100644 --- a/src/WixToolset.Core/WixStrings.Designer.cs +++ b/src/WixToolset.Core/WixStrings.Designer.cs @@ -14,7 +14,7 @@ namespace WixToolset { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class WixStrings { + public class WixStrings { private static global::System.Resources.ResourceManager resourceMan; @@ -127,7 +127,7 @@ namespace WixToolset { /// /// Looks up a localized string similar to Could not determine ProductCode from transform summary information. /// - internal static string EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo { + public static string EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo { get { return ResourceManager.GetString("EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo", resourceCulture); } @@ -226,7 +226,7 @@ namespace WixToolset { /// /// Looks up a localized string similar to Transform authored into multiple Media '{0}' and '{1}'.. /// - internal static string EXP_TransformAuthoredIntoMultipleMedia { + public static string EXP_TransformAuthoredIntoMultipleMedia { get { return ResourceManager.GetString("EXP_TransformAuthoredIntoMultipleMedia", resourceCulture); } @@ -253,7 +253,7 @@ namespace WixToolset { /// /// Looks up a localized string similar to Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'. /// - internal static string EXP_UnexpectedMergerErrorInSourceFile { + public static string EXP_UnexpectedMergerErrorInSourceFile { get { return ResourceManager.GetString("EXP_UnexpectedMergerErrorInSourceFile", resourceCulture); } @@ -262,7 +262,7 @@ namespace WixToolset { /// /// Looks up a localized string similar to Encountered an unexpected merge error of type '{0}' for which there is currently no error message to display. More information about the merge and the failure can be found in the merge log: '{1}'. /// - internal static string EXP_UnexpectedMergerErrorWithType { + public static string EXP_UnexpectedMergerErrorWithType { get { return ResourceManager.GetString("EXP_UnexpectedMergerErrorWithType", resourceCulture); } diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs index d437423c..357ff700 100644 --- a/src/WixToolset.Core/WixVariableResolver.cs +++ b/src/WixToolset.Core/WixVariableResolver.cs @@ -1,6 +1,6 @@ // 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 +namespace WixToolset.Core { using System; using System.Collections.Generic; @@ -10,11 +10,12 @@ namespace WixToolset using System.Text.RegularExpressions; using WixToolset.Data; using WixToolset.Data.Rows; + using WixToolset.Extensibility; /// /// WiX variable resolver. /// - public sealed class WixVariableResolver + internal sealed class WixVariableResolver : IBindVariableResolver { private Dictionary wixVariables; @@ -31,7 +32,7 @@ namespace WixToolset /// Gets or sets the localizer. /// /// The localizer. - public Localizer Localizer { get; private set; } + private Localizer Localizer { get; } /// /// Gets the count of variables added to the resolver. @@ -83,10 +84,7 @@ namespace WixToolset /// The resolved value. public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly) { - bool isDefault = false; - bool delayedResolve = false; - - return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); + return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, out var defaultIgnored, out var delayedIgnored); } /// @@ -97,11 +95,9 @@ namespace WixToolset /// true to only resolve localization variables; false otherwise. /// true if the resolved value was the default. /// The resolved value. - public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault) + public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, out bool isDefault) { - bool delayedResolve = false; - - return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); + return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, out isDefault, out var ignored); } /// @@ -114,9 +110,9 @@ namespace WixToolset /// true if the resolved value was the default. /// true if the value has variables that cannot yet be resolved. /// The resolved value. - public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault, ref bool delayedResolve) + public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, out bool isDefault, out bool delayedResolve) { - return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, ref isDefault, ref delayedResolve); + return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, out isDefault, out delayedResolve); } /// @@ -129,7 +125,7 @@ namespace WixToolset /// true if the resolved value was the default. /// true if the value has variables that cannot yet be resolved. /// The resolved value. - public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, ref bool isDefault, ref bool delayedResolve) + public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, out bool isDefault, out bool delayedResolve) { MatchCollection matches = Common.WixVariableRegex.Matches(value); @@ -190,10 +186,7 @@ namespace WixToolset Messaging.Instance.OnMessage(WixWarnings.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId)); } - if (null != this.Localizer) - { - resolvedValue = this.Localizer.GetLocalizedValue(variableId); - } + resolvedValue = this.Localizer?.GetLocalizedValue(variableId); } else if (!localizationOnly && "wix" == variableNamespace) { @@ -223,6 +216,7 @@ namespace WixToolset } else { + // insert the resolved value if it was found or display an error if (null != resolvedValue) { @@ -247,6 +241,19 @@ namespace WixToolset return value; } + /// + /// Try to find localization information for dialog and (optional) control. + /// + /// Dialog identifier. + /// Optional control identifier. + /// Found localization information. + /// True if localized control was found, otherwise false. + public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl) + { + localizedControl = this.Localizer?.GetLocalizedControl(dialog, control); + return localizedControl != null; + } + /// /// Resolve the delay variables in a value. /// diff --git a/src/wix/wix.csproj b/src/wix/wix.csproj index f183be39..828793ae 100644 --- a/src/wix/wix.csproj +++ b/src/wix/wix.csproj @@ -21,6 +21,8 @@ + + -- cgit v1.2.3-55-g6feb